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]
_______________________________________________
mkgmap-dev mailing list
mkgmap-dev@lists.mkgmap.org.uk
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to