I have improved calculation of the country information. In case no
country information is available the most used country in a tile is used
for all elements without country tag.
The patch now patches the trunk and no longer the index branch.
I have observed that lots of POIs are assigned the default country
although I they are assigned a different country. I have no clue where
this happens.
Maybe an index related problem? I was hoping that Steves commit today
would fix that problem but it didn't.
WanMil
I have done some development and want you to share my current findings.
1. The MapElement copy constructor seems to have a bug. The attributes
map which contains the city, region and country information is not
copied. From my point of view this is an important thing that should be
fixed in the trunk also.
2. In the patch the Locator is disabled as much as it was possible for
me up to now.
3. I am using now separate tags (mkgmap:city, mkgmap:region etc.).
I recommend to run this patch with location-autofill=-1 or 0 to see how
the patch works and not how the old Locator works.
WanMil
After the index branch has been developed so far that we can use the
results in MapSource (thanks to Steve!!) there is a big need for a
mechanism to fill missing location information.
There has been a lot of discussion about that. It stopped because it was
quite theoretical (from my point of view). Therefore I have started to
implement a prototype osm hook that tries to complete the missing
location information. I think the is_in tag is quite problematic
therefore I decided to use the addr tags.
Attached is a *very* early implementation that makes use of the boundary
polygons. It checks all nodes and ways/polygons within all boundary
polygons and fills the missing addr tags.
I think this is a better foundation for the discussion how we want to
fill the missing location information.
Before flaming me about terrible code please be aware that:
* the patch is not optimized
* it's version 1, so far away from handling all situations / countries
* it's a very early prototype
I would be happy if you try it and make some proposals how it could be
improved.
Some improvements I already know and thinking about:
* The new hook should run after the multipolygon finishing and not before
* The is_in tag should be handled
* boundary information propagation => First go through all boundaries
and add the information from higher levels to lower ones, i.e. add
addr:district to all city boundaries within that district
* Use landuse=residential information in combination with the place nodes
* In case a boundary level is missing (which mostly will be the case for
countries admin_level=2), select the addr information from all nodes and
ways within a boundary by using the most used addr-tag value. (in
germany the addr:country=DE should be the most used)
Have fun!
WanMil
Index: resources/LocatorConfig.xml
===================================================================
--- resources/LocatorConfig.xml (revision 1878)
+++ resources/LocatorConfig.xml (working copy)
@@ -312,6 +312,7 @@
<country name="France" abr="FRA">
<variant>FR</variant>
<variant>FRA</variant>
+ <variant>Metropolitan France</variant>
</country>
<country name="French Guiana" abr="GUF">
<variant>GF</variant>
@@ -649,9 +650,10 @@
<variant>NP</variant>
<variant>NPL</variant>
</country>
- <country name="Netherlands" abr="NLD">
+ <country name="Nederland" abr="NLD">
<variant>NL</variant>
<variant>NLD</variant>
+ <variant>Netherlands</variant>
</country>
<country name="New Caledonia" abr="NCL">
<variant>NC</variant>
@@ -731,9 +733,10 @@
<variant>PN</variant>
<variant>PCN</variant>
</country>
- <country name="Poland" abr="POL">
+ <country name="Polska" abr="POL">
<variant>PL</variant>
<variant>POL</variant>
+ <variant>Poland</variant>
</country>
<country name="Portugal" abr="PRT" poiDispFlag="0xc">
<variant>PT</variant>
@@ -988,6 +991,7 @@
<variant>Northern Ireland</variant>
<variant>Great Britain</variant>
<variant>Great Britian</variant>
+ <variant>Gibraltar / United Kingdom</variant>
<variant>UK</variant>
<variant>GB</variant>
<variant>GBR</variant>
@@ -996,7 +1000,6 @@
<variant>US</variant>
<variant>USA</variant>
</country>
-
<country name="United States Minor Outlying Islands" abr="UMI">
<variant>UM</variant>
<variant>UMI</variant>
Index: src/uk/me/parabola/util/ElementQuadTreeNode.java
===================================================================
--- src/uk/me/parabola/util/ElementQuadTreeNode.java (revision 0)
+++ src/uk/me/parabola/util/ElementQuadTreeNode.java (revision 0)
@@ -0,0 +1,330 @@
+package uk.me.parabola.util;
+
+import java.awt.Rectangle;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.Relation;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+
+public class ElementQuadTreeNode {
+
+ private static final Logger log = Logger.getLogger(ElementQuadTreeNode.class);
+
+ private static final int MAX_POINTS = 20;
+
+ private MultiHashMap<Coord, Element> points;
+ private final Area bounds;
+ private Area coveredBounds;
+
+ public Area getCoveredBounds() {
+ return coveredBounds;
+ }
+
+ private ElementQuadTreeNode[] children;
+
+ public static final class QuadTreePolygon {
+ private final java.awt.geom.Area javaArea;
+ private final Area bbox;
+
+ public QuadTreePolygon(java.awt.geom.Area javaArea) {
+ this.javaArea = javaArea;
+ Rectangle bboxRect = javaArea.getBounds();
+ bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y
+ + bboxRect.height, bboxRect.x + bboxRect.width);
+ }
+
+ public QuadTreePolygon(List<Coord> points) {
+ this(new java.awt.geom.Area(Java2DConverter.createPolygon(points)));
+ }
+
+ public QuadTreePolygon(Collection<List<Coord>> polygonList) {
+ this.javaArea = new java.awt.geom.Area();
+ for (List<Coord> polygon : polygonList) {
+ javaArea.add(new java.awt.geom.Area(Java2DConverter
+ .createPolygon(polygon)));
+ }
+ Rectangle bboxRect = javaArea.getBounds();
+ bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y
+ + bboxRect.height, bboxRect.x + bboxRect.width);
+ }
+
+ public Area getBbox() {
+ return bbox;
+ }
+
+ public java.awt.geom.Area getArea() {
+ return javaArea;
+ }
+ }
+
+ public ElementQuadTreeNode(Area bounds) {
+ this(bounds, Collections.<Element> emptyList());
+ }
+
+
+ public ElementQuadTreeNode(Collection<Element> elements) {
+ this.children = null;
+
+ int minLat = Integer.MAX_VALUE;
+ int maxLat = Integer.MIN_VALUE;
+ int minLong = Integer.MAX_VALUE;
+ int maxLong = Integer.MIN_VALUE;
+
+ this.points = new MultiHashMap<Coord, Element>();
+ for (Element el : elements) {
+ Collection<Coord> coords = null;
+ if (el instanceof Relation) {
+ continue;
+ } else if (el instanceof Way) {
+ Way w = (Way) el;
+ if (w.isClosed()) {
+ coords = w.getPoints().subList(0, w.getPoints().size()-1);
+ } else {
+ coords = w.getPoints();
+ }
+ } else if (el instanceof Node) {
+ coords = Collections.singleton(((Node) el).getLocation());
+ }
+
+ for (Coord c : coords) {
+ if (c.getLatitude() < minLat) {
+ minLat = c.getLatitude();
+ }
+ if (c.getLatitude() > maxLat) {
+ maxLat = c.getLatitude();
+ }
+ if (c.getLongitude() < minLong) {
+ minLong = c.getLongitude();
+ }
+ if (c.getLongitude() > maxLong) {
+ maxLong = c.getLongitude();
+ }
+ points.add(c, el);
+ }
+
+ }
+ coveredBounds = new Area(minLat, minLong, maxLat, maxLong);
+ this.bounds = coveredBounds;
+
+ if (points.size() > MAX_POINTS) {
+ split();
+ }
+ }
+
+ public ElementQuadTreeNode(Area bounds, Collection<Element> elements) {
+ this.bounds = bounds;
+ this.children = null;
+
+ int minLat = Integer.MAX_VALUE;
+ int maxLat = Integer.MIN_VALUE;
+ int minLong = Integer.MAX_VALUE;
+ int maxLong = Integer.MIN_VALUE;
+
+ this.points = new MultiHashMap<Coord, Element>();
+ for (Element el : elements) {
+ Collection<Coord> coords = null;
+ if (el instanceof Relation) {
+ continue;
+ } else if (el instanceof Way) {
+ Way w = (Way) el;
+ if (w.isClosed()) {
+ coords = w.getPoints().subList(0, w.getPoints().size()-1);
+ } else {
+ coords = w.getPoints();
+ }
+ } else if (el instanceof Node) {
+ coords = Collections.singleton(((Node) el).getLocation());
+ }
+
+ for (Coord c : coords) {
+ if (c.getLatitude() < minLat) {
+ minLat = c.getLatitude();
+ }
+ if (c.getLatitude() > maxLat) {
+ maxLat = c.getLatitude();
+ }
+ if (c.getLongitude() < minLong) {
+ minLong = c.getLongitude();
+ }
+ if (c.getLongitude() > maxLong) {
+ maxLong = c.getLongitude();
+ }
+ points.add(c, el);
+ }
+
+ }
+ coveredBounds = new Area(minLat, minLong, maxLat, maxLong);
+
+ if (points.size() > MAX_POINTS) {
+ split();
+ }
+ }
+
+ public Area getBounds() {
+ return this.bounds;
+ }
+
+ private boolean add(Coord c, Element element) {
+ if (coveredBounds == null) {
+ coveredBounds = new Area(c.getLatitude(), c.getLongitude(),
+ c.getLatitude(), c.getLongitude());
+ } else if (coveredBounds.contains(c) == false) {
+ coveredBounds = new Area(Math.min(coveredBounds.getMinLat(),
+ c.getLatitude()), Math.min(coveredBounds.getMinLong(),
+ c.getLongitude()), Math.max(coveredBounds.getMaxLat(),
+ c.getLatitude()), Math.max(coveredBounds.getMaxLong(),
+ c.getLongitude()));
+ }
+ if (isLeaf()) {
+ points.add(c,element);
+ if (points.size() > MAX_POINTS)
+ split();
+ return true;
+ } else {
+ for (ElementQuadTreeNode nodes : children) {
+ if (nodes.getBounds().contains(c)) {
+ return nodes.add(c, element);
+ }
+ }
+ return false;
+ }
+ }
+
+ public boolean add(Element c) {
+ if (c instanceof Relation) {
+ log.error("Relations are not supported by this quadtree implementation. Skipping relation "
+ + c.toBrowseURL());
+ return false;
+ } else if (c instanceof Way) {
+ // add all points to the tree
+ Way w = (Way) c;
+ List<Coord> points;
+ if (w.isClosed()) {
+ points = w.getPoints().subList(0, w.getPoints().size()-1);
+ } else {
+ points = w.getPoints();
+ }
+
+ boolean allOk = true;
+ for (Coord cp : points) {
+ allOk = add(cp, c) && allOk;
+ }
+ return allOk;
+ } else if (c instanceof Node) {
+ return add(((Node) c).getLocation(), c);
+ } else {
+ log.error("Unsupported element type: "+c);
+ return false;
+ }
+ }
+
+ public MultiHashMap<Coord, Element> get(Area bbox, MultiHashMap<Coord, Element> resultList) {
+ if (isLeaf()) {
+ if (bbox.getMinLat() <= coveredBounds.getMinLat()
+ && bbox.getMaxLat() >= coveredBounds.getMaxLat()
+ && bbox.getMinLong() <= coveredBounds.getMinLong()
+ && bbox.getMaxLong() >= coveredBounds.getMaxLong()) {
+
+ // the bounding box is contained completely in the bbox
+ // => add all points without further check
+ resultList.putAll(points);
+ } else {
+ // check each point
+ for (Entry<Coord, List<Element>> e : points.entrySet()) {
+ if (bbox.contains(e.getKey())) {
+ for (Element el : e.getValue())
+ resultList.add(e.getKey(), el);
+ }
+ }
+ }
+ } else {
+ for (ElementQuadTreeNode child : children) {
+ if (bbox.intersects(child.getCoveredBounds())) {
+ resultList = child.get(bbox, resultList);
+ }
+ }
+ }
+ return resultList;
+ }
+
+ public MultiHashMap<Coord, Element> get(QuadTreePolygon polygon,
+ MultiHashMap<Coord, Element> resultList) {
+ if (polygon.getBbox().intersects(getBounds())) {
+ if (isLeaf()) {
+ for (Entry<Coord, List<Element>> e : points.entrySet()) {
+ if (polygon.getArea().contains(e.getKey().getLongitude(),
+ e.getKey().getLatitude())) {
+ for (Element el : e.getValue())
+ resultList.add(e.getKey(),el);
+ }
+ }
+ } else {
+ for (ElementQuadTreeNode child : children) {
+ if (polygon.getBbox().intersects(child.getBounds())) {
+ java.awt.geom.Area subArea = (java.awt.geom.Area) polygon
+ .getArea().clone();
+ subArea.intersect(createArea(child.getBounds()));
+ child.get(new QuadTreePolygon(subArea), resultList);
+ }
+ }
+ }
+ }
+ return resultList;
+
+ }
+
+ private java.awt.geom.Area createArea(Area bbox) {
+ return new java.awt.geom.Area(new Rectangle(bbox.getMinLong(),
+ bbox.getMinLat(), bbox.getWidth(), bbox.getHeight()));
+ }
+
+ public boolean isLeaf() {
+ return points != null;
+ }
+
+ private void split() {
+ if (bounds.getHeight() <= 1 || bounds.getWidth() <= 1) {
+ return;
+ }
+
+ int halfLat = (bounds.getMinLat() + bounds.getMaxLat()) / 2;
+ int halfLong = (bounds.getMinLong() + bounds.getMaxLong()) / 2;
+ children = new ElementQuadTreeNode[4];
+
+ Area swBounds = new Area(bounds.getMinLat(), bounds.getMinLong(),
+ halfLat, halfLong);
+ Area nwBounds = new Area(halfLat + 1, bounds.getMinLong(),
+ bounds.getMaxLat(), halfLong);
+ Area seBounds = new Area(bounds.getMinLat(), halfLong + 1, halfLat,
+ bounds.getMaxLong());
+ Area neBounds = new Area(halfLat + 1, halfLong + 1, bounds.getMaxLat(),
+ bounds.getMaxLong());
+
+ children[0] = new ElementQuadTreeNode(swBounds);
+ children[1] = new ElementQuadTreeNode(nwBounds);
+ children[2] = new ElementQuadTreeNode(seBounds);
+ children[3] = new ElementQuadTreeNode(neBounds);
+
+ MultiHashMap<Coord, Element> copyPoints = points;
+ points = null;
+ for (Entry<Coord, List<Element>> c : copyPoints.entrySet()) {
+ for (Element el : c.getValue())
+ add(c.getKey(), el);
+ }
+ }
+
+ public void clear() {
+ this.children = null;
+ points = new MultiHashMap<Coord, Element>();
+ coveredBounds = new Area(Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Integer.MIN_VALUE, Integer.MIN_VALUE);
+ }
+}
Index: src/uk/me/parabola/util/MultiHashMap.java
===================================================================
--- src/uk/me/parabola/util/MultiHashMap.java (revision 1878)
+++ src/uk/me/parabola/util/MultiHashMap.java (working copy)
@@ -21,7 +21,7 @@
* @return a list of values for the given keys or the empty list of no such
* value exist.
*/
- public List<V> get(K key) {
+ public List<V> get(Object key) {
List<V> result = super.get(key);
return result == null ? emptyList : result;
}
Index: src/uk/me/parabola/util/ElementQuadTree.java
===================================================================
--- src/uk/me/parabola/util/ElementQuadTree.java (revision 0)
+++ src/uk/me/parabola/util/ElementQuadTree.java (revision 0)
@@ -0,0 +1,135 @@
+package uk.me.parabola.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.util.ElementQuadTreeNode.QuadTreePolygon;
+
+public class ElementQuadTree {
+
+ private final ElementQuadTreeNode root;
+ private long itemCount;
+
+ public ElementQuadTree(Area bbox) {
+ this.root = new ElementQuadTreeNode(bbox);
+ this.itemCount = 0;
+ }
+
+ public ElementQuadTree(Collection<Element> elements) {
+ this.root = new ElementQuadTreeNode(elements);
+ this.itemCount = 0;
+ }
+
+ public boolean addAll(Collection<Element> elements) {
+ boolean oneAdded = false;
+ for (Element element : elements) {
+ oneAdded = add(element) | oneAdded;
+ }
+ return oneAdded;
+ }
+
+ public boolean add(Element element) {
+
+ boolean added = root.add(element);
+ if (added) {
+ itemCount++;
+ }
+ return added;
+ }
+
+ public MultiHashMap<Coord, Element> get(Area bbox) {
+ return root.get(bbox, new MultiHashMap<Coord, Element>());
+ }
+
+ public MultiHashMap<Coord, Element> get(java.awt.geom.Area polygon) {
+ return root.get(new QuadTreePolygon(polygon), new MultiHashMap<Coord, Element>());
+ }
+
+ public MultiHashMap<Coord, Element> get(Collection<List<Coord>> polygons) {
+ return root.get(new QuadTreePolygon(polygons),
+ new MultiHashMap<Coord, Element>());
+ }
+
+ public MultiHashMap<Coord, Element> get(List<Coord> polygon) {
+ return get(polygon, 0);
+ }
+
+ public MultiHashMap<Coord, Element> get(List<Coord> polygon, int offset) {
+ if (polygon.size() < 3) {
+ return new MultiHashMap<Coord, Element>();
+ }
+ if (polygon.get(0).equals(polygon.get(polygon.size() - 1)) == false) {
+ return null;
+ }
+ MultiHashMap<Coord, Element> points = root.get(new QuadTreePolygon(polygon),
+ new MultiHashMap<Coord, Element>());
+ if (offset > 0) {
+ for (Coord c : new ArrayList<Coord>(points.keySet())) {
+ if (isCloseToPolygon(c, polygon, offset)) {
+ points.remove(c);
+ }
+ }
+ }
+ return points;
+ }
+
+ public void clear() {
+ itemCount = 0;
+ root.clear();
+ }
+
+ // TODO itemCount is not a good counter (coords or elements?)
+ private long getSize() {
+ return itemCount;
+ }
+
+ private boolean isCloseToPolygon(Coord point, List<Coord> polygon,
+ int gap) {
+ Iterator<Coord> polyIter = polygon.iterator();
+ Coord c2 = polyIter.next();
+ while (polyIter.hasNext()) {
+ Coord c1 = c2;
+ c2 = polyIter.next();
+ double dist = distanceToSegment(c1, c2, point);
+ if (dist <= gap) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Calculates the distance to the given segment in meter.
+ * @param spoint1 segment point 1
+ * @param spoint2 segment point 2
+ * @param point point
+ * @return the distance in meter
+ */
+ private double distanceToSegment(Coord spoint1, Coord spoint2, Coord point) {
+
+ double dx = spoint2.getLongitude() - spoint1.getLongitude();
+ double dy = spoint2.getLatitude() - spoint1.getLatitude();
+
+ if ((dx == 0) && (dy == 0)) {
+ return spoint1.distance(point);
+ }
+
+ double frac = ((point.getLongitude() - spoint1.getLongitude()) * dx + (point
+ .getLatitude() - spoint1.getLatitude()) * dy)
+ / (dx * dx + dy * dy);
+
+ if (frac < 0) {
+ return spoint1.distance(point);
+ } else if (frac > 1) {
+ return spoint2.distance(point);
+ } else {
+ return spoint1.makeBetweenPoint(spoint2, frac).distance(point);
+ }
+
+ }
+}
Index: src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java (working copy)
@@ -52,7 +52,9 @@
private Style style;
private final OsmReadingHooks[] POSSIBLE_HOOKS = {
new SeaGenerator(),
+ new MultiPolygonFinishHook(),
new HighwayHooks(),
+ new AddressHook(),
};
private OsmConverter converter;
private final Set<String> usedTags = new HashSet<String>();
Index: src/uk/me/parabola/mkgmap/reader/osm/Way.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Way.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/reader/osm/Way.java (working copy)
@@ -18,6 +18,7 @@
import java.awt.*;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import uk.me.parabola.imgfmt.Utils;
@@ -92,12 +93,7 @@
}
public void reverse() {
- int numPoints = points.size();
- for(int i = 0; i < numPoints/2; ++i) {
- Coord t = points.get(i);
- points.set(i, points.get(numPoints - 1 - i));
- points.set(numPoints - 1 - i, t);
- }
+ Collections.reverse(points);
}
public boolean isClosed() {
Index: src/uk/me/parabola/mkgmap/reader/osm/Element.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Element.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/reader/osm/Element.java (working copy)
@@ -27,6 +27,10 @@
private String name;
private long id;
+ public int getTagCount() {
+ return (tags == null ? 0 : tags.size());
+ }
+
/**
* Add a tag to the way. Some tags are recognised separately and saved in
* separate fields.
Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonFinishHook.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonFinishHook.java (revision 0)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonFinishHook.java (revision 0)
@@ -0,0 +1,46 @@
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.util.Arrays;
+
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.EnhancedProperties;
+
+public class MultiPolygonFinishHook extends OsmReadingHooksAdaptor {
+ private static final Logger log = Logger.getLogger(MultiPolygonFinishHook.class);
+
+ private ElementSaver saver;
+
+ public MultiPolygonFinishHook() {
+ }
+
+ @Override
+ public boolean init(ElementSaver saver, EnhancedProperties props) {
+ this.saver = saver;
+ return true;
+ }
+
+ @Override
+ public void end() {
+ log.error("Finish multipolygons");
+ for (Way way : saver.getWays().values()) {
+ String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG);
+ if (removeTag == null) {
+ continue;
+ }
+ if (ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY.equals(removeTag)) {
+ if (log.isDebugEnabled())
+ log.debug("Remove all tags from way",way.getId(),way.toTagString());
+ way.removeAllTags();
+ } else {
+ String[] tagsToRemove = removeTag.split(";");
+ if (log.isDebugEnabled())
+ log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString());
+ for (String rTag : tagsToRemove) {
+ way.deleteTag(rTag);
+ }
+ way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG);
+ }
+ }
+ }
+
+}
Index: src/uk/me/parabola/mkgmap/reader/osm/AddressHook.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/AddressHook.java (revision 0)
+++ src/uk/me/parabola/mkgmap/reader/osm/AddressHook.java (revision 0)
@@ -0,0 +1,246 @@
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.ElementQuadTree;
+import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.Java2DConverter;
+import uk.me.parabola.util.MultiHashMap;
+
+public class AddressHook extends OsmReadingHooksAdaptor {
+ private static final Logger log = Logger.getLogger(AddressHook.class);
+
+ private ElementSaver saver;
+
+ private static class BoundInfo {
+ private final int adminLevel;
+ private final String mkgmapTag;
+ private final Collection<String> osmTags;
+
+ public BoundInfo(int adminLevel, String mkgmapTag, String... osmTags) {
+ super();
+ this.adminLevel = adminLevel;
+ this.mkgmapTag = mkgmapTag;
+ this.osmTags = new ArrayList<String>(Arrays.asList(osmTags));
+ }
+
+ public int getAdminLevel() {
+ return adminLevel;
+ }
+
+ public String getMkgmapTag() {
+ return mkgmapTag;
+ }
+
+ public Collection<String> getOsmTags() {
+ return osmTags;
+ }
+ }
+
+ private final TreeMap<Integer, BoundInfo> levelTags = new TreeMap<Integer, BoundInfo>() {
+ {
+ put(2, new BoundInfo(2, "mkgmap:country", "addr:country", "is_in:country"));
+ put(3, new BoundInfo(3, "mkgmap:province", "addr:province"));
+ put(4, new BoundInfo(4, "mkgmap:province", "addr:province"));
+ put(5, new BoundInfo(5, "mkgmap:district", "addr:district"));
+ put(6, new BoundInfo(6, "mkgmap:subdistrict", "addr:subdistrict"));
+ put(7, new BoundInfo(7, "mkgmap:subdistrict", "addr:subdistrict"));
+ put(8, new BoundInfo(8, "mkgmap:city", "addr:city","is_in:city"));
+ put(9, new BoundInfo(9, "mkgmap:city", "addr:city","is_in:city"));
+ put(10, new BoundInfo(10, "mkgmap:city", "addr:city","is_in:city"));
+ }
+ };
+
+ public boolean init(ElementSaver saver, EnhancedProperties props) {
+ this.saver = saver;
+ return true;
+ }
+
+ public void end() {
+ log.info("Starting with address hook");
+ List<Element> elements = new ArrayList<Element>(saver.getWays().size()
+ + saver.getNodes().size());
+
+ MultiHashMap<Integer, Way> boundaries = new MultiHashMap<Integer, Way>();
+
+ for (Node node : saver.getNodes().values()) {
+ if (node.getTagCount() > 0) {
+ elements.add(node);
+ }
+ }
+ for (Way way : saver.getWays().values()) {
+ if (way.getTagCount() == 0) {
+ continue;
+ }
+ if (way.isClosed()
+ && "administrative".equals(way.getTag("boundary"))
+ && way.getTag("admin_level") != null
+ && "polyline".equals(way.getTag("mkgmap:stylefilter")) == false) {
+ {
+ try {
+ int adminlevel = Integer.valueOf(way
+ .getTag("admin_level"));
+ if (adminlevel >= 0 && adminlevel <= 10) {
+ boundaries.add(adminlevel, way);
+ }
+ } catch (NumberFormatException nfe) {
+ log.error(nfe, nfe);
+ }
+ }
+ }
+
+ // in any case add it to the element list
+ elements.add(way);
+
+ }
+
+ log.info("Creating quadtree for", elements.size(), "elements");
+ ElementQuadTree quadTree = new ElementQuadTree(elements);
+ log.info("Quadtree created");
+ elements.clear();
+
+ // first add the tags to the boundaries itself
+ for (Integer adminlevel : levelTags.keySet()) {
+ String tag = levelTags.get(adminlevel).getMkgmapTag();
+ log.info("Preparing admin level", adminlevel, "with tag", tag);
+ List<Way> levelBoundaries = boundaries.get(adminlevel);
+ log.info(levelBoundaries.size(), "boundaries");
+ for (Way boundary : levelBoundaries) {
+ String name = getName(boundary);
+ if (name == null) {
+ continue;
+ }
+
+ boundary.addTag(tag, name);
+ }
+ }
+
+ for (Integer adminlevel : levelTags.keySet()) {
+ String tag = levelTags.get(adminlevel).getMkgmapTag();
+ log.info("Processing admin level", adminlevel, "with tag", tag);
+
+ java.awt.geom.Area tileArea = Java2DConverter
+ .createBoundsArea(saver.getBoundingBox());
+
+ List<Way> levelBoundaries = boundaries.get(adminlevel);
+ log.info(levelBoundaries.size(), "boundaries");
+ for (Way boundary : levelBoundaries) {
+ String name = getName(boundary);
+ if (name == null) {
+ continue;
+ }
+
+ java.awt.geom.Area boundArea = Java2DConverter
+ .createArea(boundary.getPoints());
+ if (adminlevel <=2) {
+ tileArea.subtract(boundArea);
+ }
+
+ log.info("Checking", boundary.toTagString());
+
+ MultiHashMap<Coord, Element> elems = quadTree.get(boundArea);
+ log.info(elems.size(), "elements found");
+ HashSet<Element> uniqueElems = new HashSet<Element>();
+ for (List<Element> elemList : elems.values()) {
+ uniqueElems.addAll(elemList);
+ }
+
+ for (Element elem : uniqueElems) {
+ if (elem.getTag(tag) == null) {
+ if (log.isDebugEnabled())
+ log.debug("Add tag", tag + "=" + name, "to",
+ elem.toTagString());
+ elem.addTag(tag, name);
+ }
+ }
+ }
+
+ if (adminlevel <=2) {
+ MultiHashMap<Coord, Element> remainingElementMap = quadTree
+ .get(tileArea);
+ HashSet<Element> remainingElements = new HashSet<Element>();
+ for (List<Element> elems : remainingElementMap.values()) {
+ remainingElements.addAll(elems);
+ }
+ remainingElementMap.clear();
+
+ List<Element> tagLess = new ArrayList<Element>();
+ Collection<String> osmTags = levelTags.get(adminlevel).getOsmTags();
+ Map<String, Integer> counter = new HashMap<String, Integer>();
+ for (Element elem : remainingElements) {
+ String refTagValue = null;
+ for (String osmTag : osmTags) {
+ refTagValue = elem.getTag(osmTag);
+ if (refTagValue != null) {
+ break;
+ }
+ }
+ if (refTagValue!=null) {
+ Integer i = counter.get(refTagValue);
+ if (i == null) {
+ counter.put(refTagValue, 1);
+ } else {
+ counter.put(refTagValue, i + 1);
+ }
+ } else {
+ tagLess.add(elem);
+ }
+ }
+
+ log.error("Tag " + osmTags + " tagless: " + tagLess.size());
+ log.error("Distribution: " + counter);
+
+ String maxTag = null;
+ int maxCount = 0;
+ for (Entry<String, Integer> tagEntry : counter.entrySet()) {
+ if (maxCount < tagEntry.getValue()) {
+ maxCount = tagEntry.getValue();
+ maxTag = tagEntry.getKey();
+ }
+ }
+
+ if (maxCount > 0) {
+ for (Element elem : remainingElements) {
+ if (elem.getTag(tag)==null)
+ elem.addTag(tag, maxTag);
+ }
+ }
+ }
+ }
+ log.info("Address hook finished");
+ }
+
+ private String getName(Element element) {
+ String nameTag = element.getTag("name");
+ if (nameTag == null) {
+ return null;
+ }
+
+ String[] nameParts = nameTag.split(Pattern.quote(","));
+ if (nameParts.length == 0) {
+ return null;
+ }
+ return nameParts[0].trim().intern();
+ }
+
+ @Override
+ public Set<String> getUsedTags() {
+ Set<String> tags = new HashSet<String>();
+ tags.add("administrative");
+ tags.add("admin_level");
+ return tags;
+ }
+
+}
Index: src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java (working copy)
@@ -14,7 +14,6 @@
import java.util.AbstractMap;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
@@ -147,7 +146,7 @@
public void addRelation(Relation rel) {
String type = rel.getTag("type");
if (type != null) {
- if ("multipolygon".equals(type)) {
+ if ("multipolygon".equals(type) || "boundary".equals(type)) {
rel = createMultiPolyRelation(rel);
} else if("restriction".equals(type)) {
if (ignoreTurnRestrictions)
@@ -206,7 +205,6 @@
public void finishLoading() {
coordMap = null;
- finishMultiPolygons();
}
/**
@@ -244,29 +242,6 @@
relationMap = null;
}
-
- private void finishMultiPolygons() {
- for (Way way : wayMap.values()) {
- String removeTag = way.getTag(MKGMAP_REMOVE_TAG);
- if (removeTag == null) {
- continue;
- }
- if (MKGMAP_REMOVE_TAG_ALL_KEY.equals(removeTag)) {
- if (log.isDebugEnabled())
- log.debug("Remove all tags from way",way.getId(),way.toTagString());
- way.removeAllTags();
- } else {
- String[] tagsToRemove = removeTag.split(";");
- if (log.isDebugEnabled())
- log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString());
- for (String rTag : tagsToRemove) {
- way.deleteTag(rTag);
- }
- way.deleteTag(MKGMAP_REMOVE_TAG);
- }
- }
- }
-
/**
*
* "soft clip" each way that crosses a boundary by adding a point
@@ -564,6 +539,10 @@
map.put(p, inc);
}
+ public Map<Long, Node> getNodes() {
+ return nodeMap;
+ }
+
public Map<Long, Way> getWays() {
return wayMap;
}
Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (working copy)
@@ -680,6 +680,25 @@
if(city == null)
city = element.getTag("openGeoDB:name");
+
+ // set the results of the address hook
+ if(country == null)
+ country = element.getTag("mkgmap:country");
+
+ if(zip == null)
+ zip = element.getTag("mkgmap:postcode");
+
+ if(city == null)
+ city = element.getTag("mkgmap:city");
+ if (city == null)
+ city = element.getTag("mkgmap:subdistrict");
+ else if (region == null)
+ region = element.getTag("mkgmap:subdistrict");
+
+ if (region == null)
+ region = element.getTag("mkgmap:district");
+ if (region == null)
+ region = element.getTag("mkgmap:province");
if(city != null)
ms.setCity(city);
Index: src/uk/me/parabola/mkgmap/general/MapElement.java
===================================================================
--- src/uk/me/parabola/mkgmap/general/MapElement.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/general/MapElement.java (working copy)
@@ -50,6 +50,7 @@
minResolution = orig.minResolution;
maxResolution = orig.maxResolution;
extTypeAttributes = orig.extTypeAttributes;
+ attributes.putAll(orig.attributes);
}
/**
Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapBuilder.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/build/MapBuilder.java (working copy)
@@ -258,18 +258,22 @@
{
Country thisCountry;
- String CountryStr = p.getCountry();
- String RegionStr = p.getRegion();
+ String countryStr = p.getCountry();
+ if (countryStr != null) {
+ countryStr = locator.fixCountryString(countryStr);
+ p.setCountry(countryStr);
+ }
+ String regionStr = p.getRegion();
- if(CountryStr != null)
- thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr));
- else
+ if(countryStr != null) {
+ thisCountry = lbl.createCountry(countryStr, locator.getCountryCode(countryStr));
+ } else
thisCountry = country;
Region thisRegion;
- if(RegionStr != null)
+ if(regionStr != null)
{
- thisRegion = lbl.createRegion(thisCountry,RegionStr, null);
+ thisRegion = lbl.createRegion(thisCountry,regionStr, null);
}
else
thisRegion = region;
@@ -290,25 +294,29 @@
if(line.isRoad()) {
String cityName = line.getCity();
String cityCountryName = line.getCountry();
+ if (cityCountryName != null) {
+ cityCountryName = locator.fixCountryString(cityCountryName);
+ line.setCountry(cityCountryName);
+ }
String cityRegionName = line.getRegion();
String zipStr = line.getZip();
- if(cityName == null) {
- // Get name of next city if untagged
-
- tempPoint.setLocation(line.getLocation());
- MapPoint nextCity = locator.findNextPoint(tempPoint);
-
- if(nextCity != null) {
- cityName = nextCity.getCity();
- if(cityCountryName == null)
- cityCountryName = nextCity.getCountry();
- if(cityRegionName == null)
- cityRegionName = nextCity.getRegion();
- if(zipStr == null)
- zipStr = nextCity.getZip();
- }
- }
+// if(cityName == null) {
+// // Get name of next city if untagged
+//
+// tempPoint.setLocation(line.getLocation());
+// MapPoint nextCity = locator.findNextPoint(tempPoint);
+//
+// if(nextCity != null) {
+// cityName = nextCity.getCity();
+// if(cityCountryName == null)
+// cityCountryName = nextCity.getCountry();
+// if(cityRegionName == null)
+// cityRegionName = nextCity.getRegion();
+// if(zipStr == null)
+// zipStr = nextCity.getZip();
+// }
+// }
if(cityName != null) {
@@ -341,12 +349,15 @@
if(p.isExit()) {
processExit(map, (MapExitPoint)p);
}
- else if(!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses)) {
+ else if (!p.isCity())
+// if(!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses))
+ {
+
boolean doAutofill;
- if(locationAutofillLevel > 0 || p.isRoadNamePOI())
+// if(locationAutofillLevel > 0 || p.isRoadNamePOI())
doAutofill = true;
- else
- doAutofill = false;
+// else
+// doAutofill = false;
String countryStr = p.getCountry();
Index: src/uk/me/parabola/mkgmap/build/Locator.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/Locator.java (revision 1878)
+++ src/uk/me/parabola/mkgmap/build/Locator.java (working copy)
@@ -55,14 +55,15 @@
import java.util.Collection;
import java.util.List;
+import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.MapPoint;
import uk.me.parabola.mkgmap.general.MapPointFastFindMap;
-import uk.me.parabola.mkgmap.general.MapPointMultiMap;
public class Locator {
+ private static final Logger log = Logger.getLogger(Locator.class);
private final MapPointFastFindMap cityMap = new MapPointFastFindMap();
- private final MapPointMultiMap fuzzyCityMap = new MapPointMultiMap();
+// private final MapPointMultiMap fuzzyCityMap = new MapPointMultiMap();
private final List<MapPoint> placesMap = new ArrayList<MapPoint>();
private final LocatorConfig locConfig = new LocatorConfig();
@@ -74,21 +75,23 @@
public void addLocation(MapPoint p)
{
resolveIsInInfo(p); // Pre-process the is_in field
-
- if(autoFillLevel < 1 && p.getCity() == null)
- {
- // Without auto-fill city name is the name of the tag
- p.setCity(p.getName());
- }
+
+
+// if(autoFillLevel < 1 && p.getCity() == null)
+// {
+// // Without auto-fill city name is the name of the tag
+// p.setCity(p.getName());
+// }
if(p.getCity() != null)
{
+ log.error(p.getCity()+" "+p.getRegion()+" "+p.getCountry());
cityMap.put(p.getCity(), p);
- fuzzyCityMap.put(fuzzyDecode(p.getCity()),p);
-
- if(p.getName() != null && !p.getCity().equals(p.getName())) // Name variants ?
- fuzzyCityMap.put(fuzzyDecode(p.getName()),p);
+// fuzzyCityMap.put(fuzzyDecode(p.getCity()),p);
+//
+// if(p.getName() != null && !p.getCity().equals(p.getName())) // Name variants ?
+// fuzzyCityMap.put(fuzzyDecode(p.getName()),p);
}
else
{
@@ -259,21 +262,21 @@
}
}
- nextCityList = fuzzyCityMap.getList(fuzzyDecode(p.getCity()));
-
- if(nextCityList != null)
- {
- for (MapPoint nextCity: nextCityList)
- {
- Double dist = p.getLocation().distance(nextCity.getLocation());
-
- if(dist < minDist)
- {
- minDist = dist;
- near = nextCity;
- }
- }
- }
+// nextCityList = fuzzyCityMap.getList(fuzzyDecode(p.getCity()));
+//
+// if(nextCityList != null)
+// {
+// for (MapPoint nextCity: nextCityList)
+// {
+// Double dist = p.getLocation().distance(nextCity.getLocation());
+//
+// if(dist < minDist)
+// {
+// minDist = dist;
+// near = nextCity;
+// }
+// }
+// }
if(near != null && minDist < 30000) // Wrong hit more the 30 km away ?
return near;
@@ -300,9 +303,9 @@
String biggerCityName = aCityList.trim();
- if (fuzzy)
- nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName));
- else
+// if (fuzzy)
+// nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName));
+// else
nextCityList = cityMap.getList(biggerCityName);
if (nextCityList != null) {
@@ -340,7 +343,6 @@
}
public void resolve() {
-
if(autoFillLevel < 0)
return; // Nothing to do if auto-fill is fully disabled
@@ -420,7 +422,7 @@
if( place.getCity() != null)
{
cityMap.put(place.getName(),place);
- fuzzyCityMap.put(fuzzyDecode(place.getName()),place);
+// fuzzyCityMap.put(fuzzyDecode(place.getName()),place);
placesMap.set(i, null);
}
else if(autoFillLevel < 2 && (runCount + 1) == maxRuns)
@@ -442,21 +444,21 @@
}
- private String fuzzyDecode(String stringToDecode)
- {
-
- if(stringToDecode == null)
- return stringToDecode;
-
- String decodeString = stringToDecode.toUpperCase().trim();
-
- // German umlaut resolution
- //decodeString = decodeString.replaceAll("Ã","AE").replaceAll("Ã","UE").replaceAll("Ã","OE");
-
- //if(decodeString.equals(stringToDecode) == false)
- // System.out.println(stringToDecode + " -> " + decodeString);
-
- return (decodeString);
- }
+// private String fuzzyDecode(String stringToDecode)
+// {
+//
+// if(stringToDecode == null)
+// return stringToDecode;
+//
+// String decodeString = stringToDecode.toUpperCase().trim();
+//
+// // German umlaut resolution
+// //decodeString = decodeString.replaceAll("Ã","AE").replaceAll("Ã","UE").replaceAll("Ã","OE");
+//
+// //if(decodeString.equals(stringToDecode) == false)
+// // System.out.println(stringToDecode + " -> " + decodeString);
+//
+// return (decodeString);
+// }
}
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev