Here is an improved version of the sea polygon patch which also handles
shorelines intersecting the boundary of the map (set by the <bounds>
element).
I have tested it with Ireland - there are still some islands which are
"flooded", so the patch should not be considered "final".
Best wishes
Christian
Index: src/uk/me/parabola/mkgmap/reader/osm/Way.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Way.java (Revision 1124)
+++ src/uk/me/parabola/mkgmap/reader/osm/Way.java (Arbeitskopie)
@@ -76,6 +76,10 @@
}
}
+ public boolean isClosed() {
+ return points.get(0).equals(points.get(points.size()-1));
+ }
+
/**
* A simple representation of this way.
* @return A string with the name and start point
Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (Revision 1124)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (Arbeitskopie)
@@ -1,11 +1,10 @@
package uk.me.parabola.mkgmap.reader.osm;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
/**
@@ -16,15 +15,19 @@
*/
public class MultiPolygonRelation extends Relation {
private Way outer;
- private final Collection<Way> inners = new ArrayList<Way>();
+ private List<Way> outers = new ArrayList<Way>();
+ private List<Way> inners = new ArrayList<Way>();
+ private Map<Long, Way> myWayMap;
/**
* Create an instance based on an exsiting relation. We need to do
* this because the type of the relation is not known until after all
* its tags are read in.
* @param other The relation to base this one on.
+ * @param wayMap Map of all ways.
*/
- public MultiPolygonRelation(Relation other) {
+ public MultiPolygonRelation(Relation other, Map<Long, Way> wayMap) {
+ myWayMap = wayMap;
setId(other.getId());
for (Map.Entry<Element, String> pairs: other.getRoles().entrySet()){
addElement(pairs.getValue(), pairs.getKey());
@@ -33,10 +36,12 @@
if (value != null && pairs.getKey() instanceof Way) {
Way way = (Way) pairs.getKey();
- if (value.equals("outer"))
- outer = way;
- else if (value.equals("inner"))
+ if (value.equals("outer")){
+ outers.add(way);
+ }
+ else if (value.equals("inner")){
inners.add(way);
+ }
}
}
@@ -45,18 +50,63 @@
}
/** Process the ways in this relation.
+ * Joins way with the role "outer"
* Adds ways with the role "inner" to the way with the role "outer"
*/
public void processElements() {
- if (outer != null)
- {
+
+ if (outers != null)
+ {
+ // copy first outer way
+ Iterator<Way> it = outers.iterator();
+ if (it.hasNext()){
+ // duplicate outer way and remove tags for cascaded multipolygons
+ Way tempWay = it.next();
+ outer = new Way(-tempWay.getId());
+ outer.copyTags(tempWay);
+ for(Coord point: tempWay.getPoints()){
+ outer.addPoint(point);
+ }
+ myWayMap.put(outer.getId(), outer);
+ if (tempWay.getTags() != null){
+ tempWay.getTags().removeAll();
+ }
+ it.remove();
+ }
+
+ // if we have more than one outer way, we join them if they are parts of a long way
+ it = outers.iterator();
+ while (it.hasNext()){
+ Way tempWay = it.next();
+ if (tempWay.getPoints().get(0) == outer.getPoints().get(outer.getPoints().size()-1)){
+ for(Coord point: tempWay.getPoints()){
+ outer.addPoint(point);
+ }
+ if (tempWay.getTags() != null){
+ tempWay.getTags().removeAll();
+ }
+ it.remove();
+ it = outers.iterator();
+ }
+ }
+
for (Way w: inners) {
- if (w != null) {
- List<Coord> pts = w.getPoints();
- int[] insert = findCpa(outer.getPoints(), pts);
- if (insert[0] > 0)
- insertPoints(pts, insert[0], insert[1]);
- pts.clear();
+ if (w != null && outer!= null) {
+ int[] insert = findCpa(outer.getPoints(), w.getPoints());
+ insertPoints(w, insert[0], insert[1]);
+
+ // remove tags from inner way that are available in the outer way
+ if (outer.getTags() != null){
+ for (Map.Entry<String, String> mapTags: outer.getTags().getKeyValues().entrySet()){
+ String key = mapTags.getKey();
+ String value = mapTags.getValue();
+ if (w.getTag(key) != null){
+ if (w.getTag(key).equals(value)){
+ w.deleteTag(key);
+ }
+ }
+ }
+ }
}
}
}
@@ -64,22 +114,43 @@
/**
* Insert Coordinates into the outer way.
- * @param inList List of Coordinates to be inserted
+ * @param way Way to be inserted
* @param out Coordinates will be inserted after this point in the outer way.
* @param in Points will be inserted starting at this index,
* then from element 0 to (including) this element;
*/
- private void insertPoints(List<Coord> inList, int out, int in){
+ private void insertPoints(Way way, int out, int in){
List<Coord> outList = outer.getPoints();
+ List<Coord> inList = way.getPoints();
int index = out+1;
- for (int i = in; i < inList.size(); i++)
+ for (int i = in; i < inList.size(); i++){
outList.add(index++, inList.get(i));
- for (int i = 0; i <= in; i++)
+ }
+ for (int i = 0; i < in; i++){
outList.add(index++, inList.get(i));
-
- //with this line commented we get triangles, when uncommented some areas disappear
- // at least in mapsource, on device itself looks OK.
- outList.add(index,outList.get(out));
+ }
+
+ if (outer.getPoints().size() < 32){
+ outList.add(index++, inList.get(in));
+ outList.add(index, outList.get(out));
+ }
+ else{
+ // we shift the nodes to avoid duplicate nodes (large areas only)
+ int oLat = outList.get(out).getLatitude();
+ int oLon = outList.get(out).getLongitude();
+ int iLat = inList.get(in).getLatitude();
+ int iLon = inList.get(in).getLongitude();
+ if (Math.abs(oLat - iLat) > Math.abs(oLon - iLon)){
+ int delta = (oLon > iLon)? -1 : 1;
+ outList.add(index++, new Coord(iLat + delta, iLon));
+ outList.add(index, new Coord(oLat + delta, oLon));
+ }
+ else{
+ int delta = (oLat > iLat)? 1 : -1;
+ outList.add(index++, new Coord(iLat, iLon + delta));
+ outList.add(index, new Coord(oLat, oLon + delta));
+ }
+ }
}
/**
Index: src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java (Revision 1124)
+++ src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java (Arbeitskopie)
@@ -19,9 +19,12 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
@@ -65,6 +68,7 @@
private final Map<String, Long> fakeIdMap = new HashMap<String, Long>();
private final List<Node> exits = new ArrayList<Node>();
private final List<Way> motorways = new ArrayList<Way>();
+ private final List<Way> shoreline = new ArrayList<Way>();
private static final int MODE_NODE = 1;
private static final int MODE_WAY = 2;
@@ -92,6 +96,7 @@
private final boolean ignoreTurnRestrictions;
private final boolean linkPOIsToWays;
private final boolean routing;
+ private final boolean generateSea;
private final Double minimumArcLength;
private final String frigRoundabouts;
@@ -105,6 +110,7 @@
}
linkPOIsToWays = props.getProperty("link-pois-to-ways", false);
ignoreBounds = props.getProperty("ignore-osm-bounds", false);
+ generateSea = props.getProperty("generate-sea", false);
routing = props.containsKey("route");
String rsa = props.getProperty("remove-short-arcs", null);
if(rsa != null)
@@ -370,6 +376,8 @@
if("motorway".equals(highway) ||
"trunk".equals(highway))
motorways.add(currentWay);
+ if(generateSea && "coastline".equals(currentWay.getTag("natural")))
+ shoreline.add(currentWay);
currentWay = null;
// ways are processed at the end of the document,
// may be changed by a Relation class
@@ -399,7 +407,7 @@
String type = currentRelation.getTag("type");
if (type != null) {
if ("multipolygon".equals(type))
- currentRelation = new MultiPolygonRelation(currentRelation);
+ currentRelation = new MultiPolygonRelation(currentRelation, wayMap);
else if("restriction".equals(type)) {
if(ignoreTurnRestrictions)
@@ -446,6 +454,10 @@
}
coordMap = null;
+
+ if (generateSea)
+ generateSeaPolygon(shoreline);
+
for (Relation r : relationMap.values())
converter.convertRelation(r);
@@ -707,7 +719,7 @@
}
currentWay.addPoint(co);
co.incHighwayCount(); // nodes (way joins) will have highwayCount > 1
- if(minimumArcLength != null)
+ if (minimumArcLength != null || generateSea)
nodeIdMap.put(co, id);
}
}
@@ -746,4 +758,267 @@
return fakeIdVal;
}
}
+
+ private void generateSeaPolygon(List<Way> shoreline) {
+
+ Area seaBounds;
+ if (bbox != null)
+ seaBounds = bbox;
+ else
+ seaBounds = mapper.getBounds();
+
+ log.info("generating sea, seaBounds=", seaBounds);
+ int minLat = seaBounds.getMinLat();
+ int maxLat = seaBounds.getMaxLat();
+ int minLong = seaBounds.getMinLong();
+ int maxLong = seaBounds.getMaxLong();
+ Coord nw = new Coord(minLat, minLong);
+ Coord ne = new Coord(minLat, maxLong);
+ Coord sw = new Coord(maxLat, minLong);
+ Coord se = new Coord(maxLat, maxLong);
+
+ long seaId = (1L << 62) + nextFakeId++;
+ Way sea = new Way(seaId);
+ sea.addPoint(nw);
+ sea.addPoint(sw);
+ sea.addPoint(se);
+ sea.addPoint(ne);
+ sea.addPoint(nw);
+ sea.addTag("natural", "sea");
+ log.info("sea: ", sea);
+ wayMap.put(seaId, sea);
+
+ long multiId = (1L << 62) + nextFakeId++;
+ Relation seaRelation = new GeneralRelation(multiId);
+ seaRelation.addTag("type", "multipolygon");
+ seaRelation.addElement("outer", sea);
+
+ List<Way> islands = new ArrayList();
+
+ // handle islands (closes shoreline components) first (they're easy)
+ Iterator<Way> it = shoreline.iterator();
+ while (it.hasNext()) {
+ Way w = it.next();
+ if (w.isClosed()) {
+ islands.add(w);
+ it.remove();
+ }
+ }
+ concatenateWays(shoreline);
+ // there may be more islands now
+ it = shoreline.iterator();
+ while (it.hasNext()) {
+ Way w = it.next();
+ if (w.isClosed()) {
+ log.debug("island after concatenating\n");
+ islands.add(w);
+ it.remove();
+ }
+ }
+
+ for (Way w : islands) {
+ seaRelation.addElement("inner", w);
+ }
+
+ SortedMap<EdgeHit, Way> hitMap = new TreeMap<EdgeHit, Way>();
+ for (Way w : shoreline) {
+ List<Coord> points = w.getPoints();
+ Coord pStart = points.get(0);
+ Coord pEnd = points.get(points.size()-1);
+
+ EdgeHit hStart = getEdgeHit(seaBounds, pStart);
+ EdgeHit hEnd = getEdgeHit(seaBounds, pEnd);
+ if (hStart == null || hEnd == null) {
+ log.error(String.format("Non-closed coastline segment does not hit bounding box - expect strange results: %d (%s) %d (%s) %s\n",
+ nodeIdMap.get(pStart), pStart.toDegreeString(),
+ nodeIdMap.get(pEnd), pEnd.toDegreeString(),
+ pStart.toOSMURL()));
+ }
+ else {
+ log.debug("hits: ", hStart, hEnd);
+ hitMap.put(hStart, w);
+ hitMap.put(hEnd, null);
+ }
+ }
+
+ if (hitMap.size() > 0) {
+ Way w = new Way((1L << 62) + nextFakeId++);
+ Iterator<EdgeHit> it1;
+ it1=hitMap.keySet().iterator();
+ EdgeHit hit = it1.next();
+ while (hit != null) {
+ Way seg = hitMap.get(hit);
+ if (seg != null) {
+ log.info("adding shoreline ", w);
+ w.getPoints().addAll(seg.getPoints());
+ if (it1.hasNext())
+ hit = it1.next();
+ else
+ hit = null;
+ }
+ else {
+ EdgeHit h1 = it1.next();
+ Coord p;
+ p = hit.getPoint(seaBounds);
+ log.debug("way: ", hit, p);
+ w.addPoint(p);
+ if (hit.edge < h1.edge) {
+ log.info("joining: ", hit, h1);
+ for (int i=hit.edge; i<h1.edge; i++) {
+ hit = new EdgeHit(i, 1.0);
+ p = hit.getPoint(seaBounds);
+ log.debug("way: ", hit, p);
+ w.addPoint(p);
+ }
+ }
+ else if (hit.edge > h1.edge) {
+ log.info("joining: ", hit, h1);
+ for (int i=hit.edge; i<4; i++) {
+ hit = new EdgeHit(i, 1.0);
+ p = hit.getPoint(seaBounds);
+ log.debug("way: ", hit, p);
+ w.addPoint(p);
+ }
+ for (int i=0; i<h1.edge; i++) {
+ hit = new EdgeHit(i, 1.0);
+ p = hit.getPoint(seaBounds);
+ log.debug("way: ", hit, p);
+ w.addPoint(p);
+ }
+ }
+ w.addPoint(h1.getPoint(seaBounds));
+ hit = h1;
+ }
+ }
+ seaRelation.addElement("inner", w);
+ }
+
+ seaRelation = new MultiPolygonRelation(seaRelation, wayMap);
+ relationMap.put(multiId, seaRelation);
+ seaRelation.processElements();
+ }
+
+ /**
+ * Specifies where an edge of the bounding box is hit.
+ */
+ private static class EdgeHit implements Comparable<EdgeHit>
+ {
+ int edge;
+ double t;
+
+ EdgeHit(int edge, double t) {
+ this.edge = edge;
+ this.t = t;
+ }
+
+ public int compareTo(EdgeHit o) {
+ if (edge < o.edge)
+ return -1;
+ else if (edge > o.edge)
+ return +1;
+ else if (t < o.t)
+ return -1;
+ else if (t > o.t)
+ return 1;
+ else
+ return 0;
+ }
+
+ Coord getPoint(Area a) {
+ log.debug("getPoint: ", this, a);
+ switch (edge) {
+ case 0:
+ return new Coord(a.getMinLat(), (int) (a.getMinLong() + t * (a.getMaxLong()-a.getMinLong())));
+
+ case 1:
+ return new Coord((int)(a.getMinLat() + t * (a.getMaxLat()-a.getMinLat())), a.getMaxLong());
+
+ case 2:
+ return new Coord(a.getMaxLat(), (int)(a.getMaxLong() - t * (a.getMaxLong()-a.getMinLong())));
+
+ case 3:
+ return new Coord((int)(a.getMaxLat() - t * (a.getMaxLat()-a.getMinLat())), a.getMinLong());
+
+ default:
+ throw new RuntimeException("illegal state");
+ }
+ }
+
+ public String toString() {
+ return "EdgeHit " + edge + "@" + t;
+ }
+ }
+
+ private EdgeHit getEdgeHit(Area a, Coord p)
+ {
+ return getEdgeHit(a, p, 10);
+ }
+
+ private EdgeHit getEdgeHit(Area a, Coord p, int tolerance)
+ {
+ int lat = p.getLatitude();
+ int lon = p.getLongitude();
+ int minLat = a.getMinLat();
+ int maxLat = a.getMaxLat();
+ int minLong = a.getMinLong();
+ int maxLong = a.getMaxLong();
+
+ log.info(String.format("getEdgeHit: (%d %d) (%d %d %d %d)", lat, lon, minLat, minLong, maxLat, maxLong));
+ if (lat <= minLat+tolerance) {
+ return new EdgeHit(0, ((double)(lon - minLong))/(maxLong-minLong));
+ }
+ else if (lon >= maxLong-tolerance) {
+ return new EdgeHit(1, ((double)(lat - minLat))/(maxLat-minLat));
+ }
+ else if (lat >= maxLat-tolerance) {
+ return new EdgeHit(2, ((double)(maxLong - lon))/(maxLong-minLong));
+ }
+ else if (lon <= minLong+tolerance) {
+ return new EdgeHit(3, ((double)(maxLat - lat))/(maxLat-minLat));
+ }
+ else
+ return null;
+ }
+
+ private void concatenateWays(List<Way> ways) {
+ Map<Coord, Way> beginMap = new HashMap();
+
+ for (Way w : ways) {
+ if (!w.isClosed()) {
+ List<Coord> points = w.getPoints();
+ beginMap.put(points.get(0), w);
+ }
+ }
+
+ int merged = 1;
+ while (merged > 0) {
+ merged = 0;
+ for (Way w1 : ways) {
+ if (w1.isClosed()) continue;
+
+ List<Coord> points1 = w1.getPoints();
+ Way w2 = beginMap.get(points1.get(points1.size()-1));
+ if (w2 != null) {
+ log.info("merging: ", ways.size(), w1.getId(), w2.getId());
+ List<Coord> points2 = w2.getPoints();
+ Way wm;
+ if (w1.getId() < (1L << 62)) {
+ wm = new Way((1L << 62) + nextFakeId++);
+ ways.remove(w1);
+ ways.add(wm);
+ wm.getPoints().addAll(points1);
+ beginMap.put(points1.get(0), wm);
+ }
+ else {
+ wm = w1;
+ }
+ wm.getPoints().addAll(points2);
+ ways.remove(w2);
+ beginMap.remove(points2.get(0));
+ merged++;
+ break;
+ }
+ }
+ }
+ }
}
Index: src/uk/me/parabola/mkgmap/reader/osm/Element.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Element.java (Revision 1124)
+++ src/uk/me/parabola/mkgmap/reader/osm/Element.java (Arbeitskopie)
@@ -86,6 +86,7 @@
* element.
*/
public void copyTags(Element other) {
+ if (other.tags != null)
tags = other.tags.copy();
}
@@ -97,4 +98,8 @@
if (this.name == null)
this.name = name;
}
+
+ public Tags getTags() {
+ return tags;
+ }
}
Index: src/uk/me/parabola/mkgmap/reader/osm/Tags.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Tags.java (Revision 1124)
+++ src/uk/me/parabola/mkgmap/reader/osm/Tags.java (Arbeitskopie)
@@ -16,7 +16,9 @@
*/
package uk.me.parabola.mkgmap.reader.osm;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
/**
* Store the tags that belong to an Element.
@@ -120,7 +122,7 @@
}
return null;
}
-
+
/**
* Make a deep copy of this object.
* @return A copy of this object.
@@ -271,4 +273,22 @@
put(e.key, e.value);
}
}
-}
+
+ public void removeAll() {
+ for (int i = 0; i < capacity; i++){
+ keys[i] = null;
+ values[i] = null;
+ }
+ size = 0;
+ }
+
+ public Map<String, String> getKeyValues() {
+ Map<String, String> tagMap = new HashMap<String, String>();
+ for (int i = 0; i < capacity; i++)
+ if (keys[i] != null && values[i] != null)
+ tagMap.put(keys[i], values[i]);
+ return tagMap;
+ }
+
+
+}
\ No newline at end of file
Index: resources/styles/default/polygons
===================================================================
--- resources/styles/default/polygons (Revision 1124)
+++ resources/styles/default/polygons (Arbeitskopie)
@@ -54,6 +54,7 @@
natural=mud [0x51 resolution 20]
natural=scrub [0x4f resolution 20]
natural=water [0x3c resolution 20]
+natural=sea [0x32 resolution 10]
natural=wood [0x50 resolution 18]
place=village [0x03 resolution 18]