package me.osm.gazetter.striper; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import me.osm.gazetter.utils.FileUtils; import me.osm.gazetter.utils.FileUtils.LineHandler; import org.apache.commons.lang3.StringUtils; import org.joda.time.LocalDateTime; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.code.externalsorting.ExternalSort; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.io.WKTReader; /** * Tracks builded boundaries and provides stored versions * for boundaries which was failed to be built. * */ public class BoundariesFallbacker { private static final Logger log = LoggerFactory.getLogger(BoundariesFallbacker.class); private String fallbackPath; private HashSet<String> fallbackTypes; private PrintWriter writer; //It shouldn't be very big, I think private Map<String, String> cache = null; private File file; private volatile static BoundariesFallbacker instance = null; public static BoundariesFallbacker getInstance(String fallbackPath, List<String> storeTypes) { if(instance == null) { synchronized (BoundariesFallbacker.class) { if(instance == null) { instance = new BoundariesFallbacker(fallbackPath, storeTypes); } } } return instance; } private BoundariesFallbacker(String fallbackPath, List<String> storeTypes) { this.fallbackPath = StringUtils.stripToNull(fallbackPath); if(this.fallbackPath != null) { this.fallbackTypes = new HashSet<String>(storeTypes); if(this.fallbackTypes == null || this.fallbackTypes.isEmpty()) { this.fallbackTypes.addAll(Arrays.asList( "boundary:2", "boundary:3", "boundary:4", "boundary:5", "boundary:6")); } try { file = new File(this.fallbackPath); if(file.exists()) { log.info("Load boundaries from {}", fallbackPath); buildCache(); } if(!file.exists()) { file.createNewFile(); } OutputStream os = new FileOutputStream(file, true); writer = new PrintWriter(new OutputStreamWriter(os, "UTF8")); } catch (Exception e) { throw new RuntimeException(e); } } } public void saveBoundary(JSONObject feature, MultiPolygon geometry) { if(writer != null) { JSONObject properties = feature.getJSONObject(GeoJsonWriter.PROPERTIES); String btype = "boundary:" + StringUtils.stripToEmpty(properties.optString("admin_level")); if(fallbackTypes.contains(btype)) { String id = StringUtils.split(feature.getString("id"), '-')[2]; String wkt = geometry.toString(); writer.println(id + "\t" + StringUtils.removeEnd(GeoJsonWriter.getNowTimestampString(), "Z") + "\t" + wkt); } } } public MultiPolygon getGeometry(String id) { if(file == null) { return null; } try { if(cache != null) { String wkt = cache.get(id); if(wkt != null) { return (MultiPolygon)new WKTReader().read(wkt); } } } catch (Exception e) { log.debug("Error duiring load geometry from fallback for {}. Error: {}", id, e.getMessage()); return null; } return null; } private void buildCache() throws IOException { if(file != null) { cache = new HashMap<String, String>(); for(String s : FileUtils.readLines(file)) { String[] split = StringUtils.split(s, '\t'); if(split != null && split.length == 3) { String id = split[0]; String geom = split[2]; cache.put(id, geom); } } log.info("Loaded {} lines.", cache.size()); } } public void close() { if(writer != null) { writer.flush(); writer.close(); } if(file != null) { sortAndMerge(); } } private class IdTimestampGeometry{ public String id = null; public LocalDateTime timestamp = null; public String geometry = null; } private void sortAndMerge() { try { ExternalSort.sort(file, file); File tmp = new File(this.fallbackPath + ".tmp"); OutputStream os = new FileOutputStream(tmp, true); final PrintWriter tmpW = new PrintWriter(new OutputStreamWriter(os, "UTF8")); final IdTimestampGeometry itg = new IdTimestampGeometry(); LineHandler handler = new LineHandler() { @Override public void handle(String s) { String[] split = StringUtils.split(s, '\t'); if(split != null && split.length >= 3) { if(split[0].equals(itg.id)) { LocalDateTime t = new LocalDateTime(StringUtils.removeEnd(split[1], "Z")); if(itg.timestamp == null || t.isAfter(itg.timestamp)) { itg.geometry = split[2]; itg.timestamp = t; } } else { if(itg.id != null) { tmpW.println(itg.id + '\t' + itg.timestamp.toString() + '\t' + itg.geometry); } itg.id = split[0]; itg.geometry = split[2]; itg.timestamp = LocalDateTime.parse(split[1]); } } } }; FileUtils.handleLines(file, handler); if(itg.id != null) { tmpW.println(itg.id + '\t' + itg.timestamp.toString() + '\t' + itg.geometry); } tmpW.flush(); tmpW.close(); tmp.renameTo(file); } catch (IOException e) { throw new RuntimeException(e); } } }