/*
* 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;
}
}