/* * Copyright (C) 2014. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ package uk.me.parabola.mkgmap.reader.osm; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import uk.me.parabola.imgfmt.app.Area; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.log.Logger; import uk.me.parabola.util.GpxCreator; import uk.me.parabola.util.QuadTree; /** * A relation used by the sea generation code. * * @author WanMil */ public class SeaPolygonRelation extends MultiPolygonRelation { private static final Logger log = Logger .getLogger(SeaPolygonRelation.class); private final QuadTree landCoords; private final QuadTree seaCoords; private boolean floodBlocker = true; private int floodBlockerGap = 40; private double floodBlockerRatio = 0.5d; private int floodBlockerThreshold = 20; private boolean debug; private final DecimalFormat format = new DecimalFormat("0.0000"); private Rule floodBlockerRules; private final String[] landTag = {"natural","land"}; public SeaPolygonRelation(Relation other, Map<Long, Way> wayMap, Area bbox) { super(other, wayMap, bbox); this.landCoords = new QuadTree(bbox); this.seaCoords = new QuadTree(bbox); // set a special type because this is not the OSM common multipolygon // relation addTag("type", "mkgmap:seapolygon"); } @Override protected boolean isAreaSizeCalculated() { return false; } protected void postProcessing() { if (isFloodBlocker()) { removeFloodedAreas(); } super.postProcessing(); } private void fillQuadTrees() { final AtomicBoolean isLand = new AtomicBoolean(false); final AtomicBoolean isSea = new AtomicBoolean(false); TypeResult fakedType = new TypeResult() { @Override public void add(Element el, GType type) { if (log.isDebugEnabled()) log.debug(el.getId(),type); if (type.getType() == 0x01) { isLand.set(true); } else if (type.getType() == 0x02) { isSea.set(true); } } }; for (Way way : getTileWayMap().values()) { if (log.isDebugEnabled()) log.debug("Check usage of way for floodblocker:", way.getId(), way.toTagString()); floodBlockerRules.resolveType(way, fakedType); if (isLand.get()) { // save these coords to check if some sea polygons floods // the land log.debug("Way", way.getId(), "identified as land"); landCoords.addAll(way.getPoints()); isLand.set(false); } else if (isSea.get()) { // save these coords to check if some sea polygons floods the // land log.debug("Way", way.getId(), "identified as sea"); seaCoords.addAll(way.getPoints()); isSea.set(false); } } } private void removeFloodedAreas() { fillQuadTrees(); // create a copy of all resulting ways - the tile way map contains only // polygons from // the sea generation ArrayList<Way> polygons = new ArrayList<Way>(getMpPolygons().values()); log.info("Starting flood blocker. Polygons to check:", getMpPolygons() .size()); String baseName = GpxCreator.getGpxBaseName(); if (debug) { GpxCreator.createAreaGpx(baseName + "bbox", getBbox()); } // go through all polygons and check if it contains too many coords of // the other type for (Way p : polygons) { boolean sea = "sea".equals(p.getTag("natural")); QuadTree goodCoords = (sea ? seaCoords : landCoords); QuadTree badCoords = (sea ? landCoords : seaCoords); String polyType = (sea ? "sea" : "land"); String otherType = (sea ? "land" : "sea"); List<Coord> minusCoords = badCoords.get(p.getPoints(), getFloodBlockerGap()); List<Coord> positiveCoords = goodCoords.get(p.getPoints()); log.info(polyType,"polygon", p.getId(), "contains", minusCoords.size(), otherType,"coords and", positiveCoords.size(), polyType,"coords."); if (minusCoords.size() > 0) { double area = MultiPolygonRelation.calcAreaSize(p.getPoints()); double ratio = ((minusCoords.size() - positiveCoords.size()) * 100000.0d / area); String areaFMT = format.format(area); String ratioFMT = format.format(ratio); log.info("Flood blocker for", polyType, "polygon", p.getId()); log.info("area",areaFMT); log.info(polyType, positiveCoords.size()); log.info(otherType, minusCoords.size()); log.info("ratio", ratioFMT); if (debug) { GpxCreator.createGpx( baseName + p.getId() + "_"+polyType+"_" + minusCoords.size() + "_" + positiveCoords.size() + "_" + ratioFMT, p.getPoints()); GpxCreator.createGpx( baseName + p.getId() + "_con_" + minusCoords.size() + "_" + positiveCoords.size() + "_" + ratioFMT, null, minusCoords); if (positiveCoords.isEmpty() == false) { GpxCreator.createGpx( baseName + p.getId() + "_pro_" + minusCoords.size() + "_" + positiveCoords.size() + "_" + ratioFMT, null, positiveCoords); } } if (minusCoords.size() - positiveCoords.size() >= getFloodBlockerThreshold() && ratio > getFloodBlockerRatio()) { log.warn("Polygon", p.getId(), "type",polyType,"seems to be wrong. Changing it to",otherType); if (sea) { p.deleteTag("natural"); p.addTag(landTag[0], landTag[1]); } else { p.deleteTag(landTag[0]); p.addTag("natural", "sea"); } // getMpPolygons().remove(p.getId()); } else { log.info("Polygon",p.getId(), "is not blocked"); } } } log.info("Flood blocker finished. Resulting polygons:", getMpPolygons() .size()); landCoords.clear(); seaCoords.clear(); } public boolean isFloodBlocker() { return floodBlocker; } public void setFloodBlocker(boolean floodBlocker) { this.floodBlocker = floodBlocker; } public int getFloodBlockerGap() { return floodBlockerGap; } public void setFloodBlockerGap(int floodBlockerGap) { this.floodBlockerGap = floodBlockerGap; } public double getFloodBlockerRatio() { return floodBlockerRatio; } public void setFloodBlockerRatio(double floodBlockerRatio) { this.floodBlockerRatio = floodBlockerRatio; } public int getFloodBlockerThreshold() { return floodBlockerThreshold; } public void setFloodBlockerThreshold(int floodBlockerThreshold) { this.floodBlockerThreshold = floodBlockerThreshold; } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public Rule getFloodBlockerRules() { return floodBlockerRules; } public void setFloodBlockerRules(Rule floodBlockerRules) { this.floodBlockerRules = floodBlockerRules; } public void setLandTag(String landTag, String landValue) { this.landTag[0] = landTag; this.landTag[1] = landValue; } }