[PATCH] POI Address + Area POIs v7 R942

Hi, I have integrated Ben Konrath's Area POI patch into my code. I did the following changes to Ben's code: - Replaced the hard coded area to poi type mapping with the build in rule sets. Shapes are checked against the point rules to add missing pois. - Replaced the linear search (to avoid duplicated pois) with tile based search - Add buildings to polygons style - Moved my helper classes to general directory You have to add the --add-pois-to-areas to enable the Area POIs generation. I'm not sure if we should generate a new rule set in the style folder for this purpose. It works pretty good using the point rules but it might be confusing. Thanks Ben for providing the ray cast code to check for points in polygons. Thanks Berni. Index: test/uk/me/parabola/mkgmap/general/PointInShapeTest.java =================================================================== --- test/uk/me/parabola/mkgmap/general/PointInShapeTest.java (.../upstream/mkgmap) (revision 0) +++ test/uk/me/parabola/mkgmap/general/PointInShapeTest.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,289 @@ +/** + * + */ +package uk.me.parabola.mkgmap.general; + + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import uk.me.parabola.imgfmt.app.Coord; + +/** + * @author ben + * + */ +public class PointInShapeTest { + + MapShape square, triangle, line; + int squareSize = 4; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Square + List<Coord> points = Arrays.asList( + new Coord(0, 0), + new Coord(0, squareSize), + new Coord(squareSize, squareSize), + new Coord(squareSize, 0), + new Coord(0,0) + ); + square = new MapShape(); + square.setPoints(points); + + // Triangle + points = Arrays.asList( + new Coord(0,0), + new Coord(4,4), + new Coord(8,0), + new Coord(0,0) + ); + triangle = new MapShape(); + triangle.setPoints(points); + + // Line + points = Arrays.asList( + new Coord(2,5), + new Coord(12,1) + ); + line = new MapShape(); + line.setPoints(points); + } + + @Test + public void testLinePointsInsideSquare() { + + // inside square, 1 unit from corners + List<Coord> points = Arrays.asList( + new Coord(1, squareSize/2), + new Coord(squareSize/2, squareSize - 1), + new Coord(squareSize - 1, squareSize/2), + new Coord(squareSize/2, 1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside square", + square.contains(coord)); + } + + // on the line + points = Arrays.asList( + new Coord(0, squareSize/2), + new Coord(squareSize/2, squareSize), + new Coord(squareSize, squareSize/2), + new Coord(squareSize/2, 0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square", + square.contains(coord)); + } + } + + @Test + public void testLinePointsOutsideSquare() { + + // outside square, 1 unit from line + List<Coord> points = Arrays.asList( + new Coord(-1, squareSize/2), + new Coord(squareSize/2, squareSize + 1), + new Coord(squareSize + 1, squareSize/2), + new Coord(squareSize/2, -1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square", + square.contains(coord)); + } + } + + @Test + public void testCornerPointsInsideSquare() { + // corner points + for (Coord cornerpoint : square.getPoints()) { + Coord co = new Coord(cornerpoint.getLatitude(), cornerpoint.getLongitude()); + assertTrue("corner point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside square", + square.contains(co)); + } + + // sub shape + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1; + int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be inside square", square.contains(co)); + } + + // tests above / below corner points, on the outside edge + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude(); + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // tests to the right / left side of corner points, on square edge + for (Coord cornerpoint : square.getPoints()) { + int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude(); + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + } + + @Test + public void testCornerPointsOutsideSquare() { + + // tests above / below corner points, outside squate + for (Coord cornerpoint : square.getPoints()) { + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude(); + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // tests to the right / left side of corner points, outside square + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude(); + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // super shape + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + } + + + @Test + public void testLinePointsInsideTriangle() { + // inside triangle, above / below lines + List<Coord> points = Arrays.asList( + new Coord(2,1), + new Coord(6,1), + new Coord(4,1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", + triangle.contains(coord)); + } + + // on lines + points = Arrays.asList( + new Coord(2,2), + new Coord(6,2), + new Coord(4,0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testLinePointsOutsideTriangle() { + // outside triangle, above / below lines + List<Coord> points = Arrays.asList( + new Coord(2,3), + new Coord(6,3), + new Coord(4,-1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testCornerPointsInsideTriangle() { + // corner points + for (Coord cornerpoint : triangle.getPoints()) { + assertTrue("point (" + cornerpoint.getLatitude() + ", " + cornerpoint.getLongitude() + ") should be outside triangle", + triangle.contains(cornerpoint)); + } + + // sub shape + List<Coord> points = Arrays.asList( + new Coord(2,1), + new Coord(4,3), + new Coord(6,1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", + triangle.contains(coord)); + } + + // beside points, on edge + points = Arrays.asList( + new Coord(1,0), + new Coord(7,0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testCornerPointsOutsideTriangle() { + // above points + for (Coord coord : triangle.getPoints()) { + Coord co = new Coord(coord.getLatitude(), coord.getLongitude() + 1); + assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside triangle", + triangle.contains(co)); + } + + // outside triangle, beside / below lines + List<Coord> points = Arrays.asList( + new Coord(-1,0), + new Coord(0,-1), + new Coord(3,4), + new Coord(5,4), + new Coord(9,0), + new Coord(8,-1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + + // super shape + for (Coord cornerpoint : triangle.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside triangle", triangle.contains(co)); + } + + } + + @Test + public void testLine() { + // midpoint + Coord co = new Coord(7,3); + assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside line", + line.contains(co)); + + } +} Index: src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -236,6 +236,10 @@ public void setMapId(int id) { mapId = id; } + + public void setPoiDisplayFlags(byte poiDisplayFlags) { + this.poiDisplayFlags = poiDisplayFlags; + } public int getMapInfoSize() { return mapInfoSize; Index: src/uk/me/parabola/imgfmt/app/trergn/TREFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -315,6 +315,10 @@ header.setBounds(area); } + public void setPoiDisplayFlags(byte b) { + header.setPoiDisplayFlags(b); + } + public String[] getCopyrights() { if (!isReadable()) throw new IllegalStateException("not open for reading"); Index: src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -16,6 +16,7 @@ */ package uk.me.parabola.imgfmt.app.lbl; +import java.util.Vector; import uk.me.parabola.imgfmt.app.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label; @@ -23,6 +24,94 @@ * @author Steve Ratcliffe */ public class POIRecord { + + class SimpleStreetPhoneNumber // Helper Class to encode Street Phone Numbers + { + /** + Street and Phone numbers can be stored in two different ways in the poi record + Simple Number that only contain digits are coded in base 11 coding. + This helper class tries to code the given number. If the number contains other + chars like in 4a the coding fails and the caller has to use a Label instead + */ + + private byte encodedNumber[] = null; + private int encodedSize = 0; + + public boolean set(String number) + { + int i = 0; + int j = 0; + int val = 0; + + // remove sourounding whitespaces to increase chance for simple encoding + + number = number.trim(); + + encodedNumber = new byte[(number.length()/2)+2]; + + while(i < number.length()) + { + int c1 = 0; + int c2 = 0; + + c1 = decodeChar(number.charAt(i)); + i++; + + if(i < number.length()) + { + c2 = decodeChar(number.charAt(i)); + i++; + } + else + c2 = 10; + + if(c1 < 0 || c1 > 10 || c2 < 0 || c2 > 10) // Only 0-9 and - allowed + { + return false; + } + + val = c1 * 11 + c2; // Encode as base 11 + + if(i < 3 || i >= number.length()) // first byte needs special marking with 0x80 + val = val | 0x80; // If this is not set would be treated as label pointer + + encodedNumber[j++] = (byte)val; + } + + if((val & 0x80) == 0 || i < 3) // terminate string with 0x80 if not done + { + val = 0xF8; + encodedNumber[j++] = (byte)val; + } + + encodedSize = j; + + return true; + } + + public void write(ImgFileWriter writer) + { + for(int i = 0; i < encodedSize; i++) + writer.put(encodedNumber[i]); + } + + public boolean isUsed() + { + return (encodedSize > 0); + } + + public int getSize() + { + return encodedSize; + } + + private int decodeChar(char ch) + { + return (ch - '0'); + } + + } + public static final byte HAS_STREET_NUM = 0x01; public static final byte HAS_STREET = 0x02; public static final byte HAS_CITY = 0x04; @@ -43,13 +132,16 @@ private int offset = -1; private Label poiName; - private int streetNumber; + private SimpleStreetPhoneNumber simpleStreetNumber = new SimpleStreetPhoneNumber(); + private SimpleStreetPhoneNumber simplePhoneNumber = new SimpleStreetPhoneNumber(); + private Label streetName; private Label streetNumberName; // Used for numbers such as 221b + private Label complexPhoneNumber; // Used for numbers such as 221b + + private City city = null; + private char zipIndex = 0; - private char cityIndex ; - private char zipIndex ; - private String phoneNumber; public void setLabel(Label label) { @@ -60,14 +152,35 @@ this.streetName = label; } + public boolean setSimpleStreetNumber(String streetNumber) + { + return simpleStreetNumber.set(streetNumber); + } + + public void setComplexStreetNumber(Label label) + { + streetNumberName = label; + } + + public boolean setSimplePhoneNumber(String phone) + { + return simplePhoneNumber.set(phone); + } + + public void setComplexPhoneNumber(Label label) + { + complexPhoneNumber = label; + } + + public void setZipIndex(int zipIndex) { this.zipIndex = (char) zipIndex; } - public void setCityIndex(int cityIndex) + public void setCity(City city) { - this.cityIndex = (char) cityIndex; + this.city = city; } void write(ImgFileWriter writer, byte POIGlobalFlags, int realofs, @@ -82,11 +195,21 @@ if (POIGlobalFlags != getPOIFlags()) writer.put(getWrittenPOIFlags(POIGlobalFlags)); + if (streetNumberName != null) + { + int labOff = streetNumberName.getOffset(); + writer.put((byte)((labOff & 0x7F0000) >> 16)); + writer.putChar((char)(labOff & 0xFFFF)); + } + else if (simpleStreetNumber.isUsed()) + simpleStreetNumber.write(writer); + if (streetName != null) writer.put3(streetName.getOffset()); - if (cityIndex > 0) + if (city != null) { + char cityIndex = (char) city.getIndex(); if(numCities > 255) writer.putChar(cityIndex); else @@ -100,44 +223,60 @@ else writer.put((byte)zipIndex); } + + if (complexPhoneNumber != null) + { + int labOff = complexPhoneNumber.getOffset(); + writer.put((byte)((labOff & 0x7F0000) >> 16)); + writer.putChar((char)(labOff & 0xFFFF)); + } + else if (simplePhoneNumber.isUsed()) + simplePhoneNumber.write(writer); } byte getPOIFlags() { byte b = 0; if (streetName != null) b |= HAS_STREET; - if (cityIndex > 0) + if (simpleStreetNumber.isUsed() || streetNumberName != null) + b |= HAS_STREET_NUM; + if (city != null) b |= HAS_CITY; if (zipIndex > 0) - b |= HAS_ZIP; + b |= HAS_ZIP; + if (simplePhoneNumber.isUsed() || complexPhoneNumber != null) + b |= HAS_PHONE; return b; } byte getWrittenPOIFlags(byte POIGlobalFlags) { - int mask; - int flag = 0; - int j = 0; + int mask; + int flag = 0; + int j = 0; - int usedFields = getPOIFlags(); + int usedFields = getPOIFlags(); - /* the local POI flag is really tricky - if a bit is not set in the global mask - we have to skip this bit in the local mask. - In other words the meaning of the local bits - change influenced by the global bits */ + /* the local POI flag is really tricky if a bit is not set in the global mask + we have to skip this bit in the local mask. In other words the meaning of the local bits + change influenced by the global bits */ + + for(byte i = 0; i < 6; i++) + { + mask = 1 << i; - for (byte i = 0; i < 6; i++) { - mask = 1 << i; - - if ((mask & POIGlobalFlags) == mask) { - if ((mask & usedFields) == mask) - flag |= (1 << j); - j++; + if((mask & POIGlobalFlags) == mask) + { + if((mask & usedFields) == mask) + flag = flag | (1 << j); + j++; + } + } - } - return (byte) flag; + flag = flag | 0x80; // gpsmapedit asserts for this bit set + + return (byte) flag; } /** @@ -150,9 +289,17 @@ int size = 3; if (POIGlobalFlags != getPOIFlags()) size += 1; + if (simpleStreetNumber.isUsed()) + size += simpleStreetNumber.getSize(); + if (streetNumberName != null) + size += 3; + if (simplePhoneNumber.isUsed()) + size += simplePhoneNumber.getSize(); + if (complexPhoneNumber != null) + size += 3; if (streetName != null) - size += 3; - if (cityIndex > 0) + size += 3; + if (city != null) { /* depending on how many cities are in the LBL block we have @@ -160,9 +307,9 @@ */ if(numCities > 255) - size += 2; + size += 2; else - size += 1; + size += 1; } if (zipIndex > 0) { Index: src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -20,6 +20,8 @@ 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.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label; @@ -33,7 +35,8 @@ public class PlacesFile { private final Map<String, Country> countries = new LinkedHashMap<String, Country>(); private final Map<String, Region> regions = new LinkedHashMap<String, Region>(); - private final List<City> cities = new ArrayList<City>(); + private final Map<String, City> cities = new LinkedHashMap<String, City>(); + private final SortedMap<String, City> cityList = new TreeMap<String, City>(); private final Map<String, Zip> postalCodes = new LinkedHashMap<String, Zip>(); private final List<POIRecord> pois = new ArrayList<POIRecord>(); @@ -62,8 +65,12 @@ r.write(writer); placeHeader.endRegions(writer.position()); - for (City c : cities) + for (String s : cityList.keySet()) + { + City c = cityList.get(s); c.write(writer); + } + placeHeader.endCity(writer.position()); int poistart = writer.position(); @@ -79,56 +86,122 @@ } Country createCountry(String name, String abbr) { - Country c = new Country(countries.size()+1); - + String s = abbr != null ? name + (char)0x1d + abbr : name; + + Country c = countries.get(s); + + if(c == null) + { + c = new Country(countries.size()+1); - Label l = lblFile.newLabel(s); - c.setLabel(l); - - countries.put(name, c); + Label l = lblFile.newLabel(s); + c.setLabel(l); + countries.put(s, c); + } return c; } Region createRegion(Country country, String name, String abbr) { - Region r = new Region(country, regions.size()+1); - + String s = abbr != null ? name + (char)0x1d + abbr : name; - Label l = lblFile.newLabel(s); - r.setLabel(l); - - regions.put(name, r); + String uniqueRegionName = s.toUpperCase().concat(Long.toString(country.getIndex())); + + Region r = regions.get(uniqueRegionName); + + if(r == null) + { + r = new Region(country, regions.size()+1); + Label l = lblFile.newLabel(s); + r.setLabel(l); + regions.put(uniqueRegionName, r); + } return r; } - City createCity(Country country, String name) { - City c = new City(country, cities.size()+1); + City createCity(Country country, String name, boolean unique) { + + String uniqueCityName = name.toUpperCase().concat("_C").concat(Long.toString(country.getIndex())); + + City c = null; - Label l = lblFile.newLabel(name); - c.setLabel(l); // label may be ignored if pointref is set + if(!unique) + c = cities.get(uniqueCityName); + + if(c == null) + { + c = new City(country); - cities.add(c); + Label l = lblFile.newLabel(name); + c.setLabel(l); + + /* + Adding 0 in between is important to get right sort order !!! + We have to make sure that "Kirchdorf" gets sorted before "Kirchdorf am Inn" + If this order is not correct nuvi would not find right city + */ + + cityList.put(name + " 0" + c, c); + cities.put(uniqueCityName, c); + } + return c; - } + } - City createCity(Region region, String name) { - City c = new City(region, cities.size()+1); + City createCity(Region region, String name, boolean unique) { + + String uniqueCityName = name.toUpperCase().concat("_R").concat(Long.toString(region.getIndex())); + + City c = null; - Label l = lblFile.newLabel(name); - c.setLabel(l); // label may be ignored if pointref is set + if(!unique) + c = cities.get(uniqueCityName); + + if(c == null) + { + c = new City(region); - cities.add(c); + Label l = lblFile.newLabel(name); + c.setLabel(l); + + /* + Adding 0 in between is important to get right sort order !!! + We have to make sure that "Kirchdorf" gets sorted before "Kirchdorf am Inn" + If this order is not correct nuvi would not find right city + */ + + cityList.put(name + " 0" + c, c); + cities.put(uniqueCityName, c); + } + return c; } + private void sortCities() + { + int index = 1; + + for (String s : cityList.keySet()) + { + City c = cityList.get(s); + c.setIndex(index++); + } + } + Zip createZip(String code) { - Zip z = new Zip(postalCodes.size()+1); + + Zip z = postalCodes.get(code); + + if(z == null) + { + z = new Zip(postalCodes.size()+1); - Label l = lblFile.newLabel(code); - z.setLabel(l); + Label l = lblFile.newLabel(code); + z.setLabel(l); - postalCodes.put(code, z); + postalCodes.put(code, z); + } return z; } @@ -146,6 +219,9 @@ } void allPOIsDone() { + + sortCities(); + poisClosed = true; byte poiFlags = 0; Index: src/uk/me/parabola/imgfmt/app/lbl/City.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/City.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/City.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -30,7 +30,7 @@ private static final int POINT_REF = 0x8000; private static final int REGION_IS_COUNTRY = 0x4000; - private final int index; + private int index = -1; private final Region region; private final Country country; @@ -49,13 +49,13 @@ // null if the location is being specified. private Label label; - public City(Region region, int index) { + public City(Region region) { this.region = region; this.country = null; this.index = index; } - public City(Country country, int index) { + public City(Country country) { this.country = country; this.region = null; this.index = index; @@ -83,9 +83,15 @@ } public int getIndex() { + if (index == -1) + throw new IllegalStateException("Offset not known yet."); return index; } + public void setIndex(int index) { + this.index = index; + } + public void setLabel(Label label) { pointRef = false; this.label = label; Index: src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -147,14 +147,18 @@ return places.createRegion(country, region, abbr); } - public City createCity(Region region, String city) { - return places.createCity(region, city); + public City createCity(Region region, String city, boolean unique) { + return places.createCity(region, city, unique); } - public City createCity(Country country, String city) { - return places.createCity(country, city); + public City createCity(Country country, String city, boolean unique) { + return places.createCity(country, city, unique); } + public Zip createZip(String code) { + return places.createZip(code); + } + public void allPOIsDone() { places.allPOIsDone(); } Index: src/uk/me/parabola/imgfmt/app/map/Map.java =================================================================== --- src/uk/me/parabola/imgfmt/app/map/Map.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/map/Map.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -225,6 +225,14 @@ treFile.addPolygonOverview(ov); } + /** + * Set the point of interest flags. + * @param flags The POI flags. + */ + public void setPoiDisplayFlags(int flags) { + treFile.setPoiDisplayFlags((byte) flags); + } + public void addMapObject(MapObject item) { rgnFile.addMapObject(item); } Index: src/uk/me/parabola/mkgmap/build/Locator.java =================================================================== --- src/uk/me/parabola/mkgmap/build/Locator.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/Locator.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * The Locator tries to fill missing country, region, postal coude information + * + * The algorithm works like this: + * + * 1. Step: Go through all cities an check if they have useful country region info + * The best case is if the tags is_in:country and is_in:county are present thats easy. + * Some cities have is_in information that can be used. We check for three different + * formats: + * + * County, State, Country, Continent + * County, State, Country + * Continent, Country, State, County, ... + * + * In "openGeoDb countries" this info is pretty reliable since it was imported from a + * external db into osm. Other countries have very sparse is_in info. + * + * All this cities that have such info will end up in "city" list. All which lack + * such information in "location" list. + * + * 2. Step: Go through the "location" list and check if the is_in info has some relations + * to the cities we have info about. + * + * Especially hamlets often have no full is_in information. They only have one entry in + * is_in that points to the city they belong to. I will check if I can find the name + * of this city in the "City" list. If there are more with the same name I use the + * closest one. If we can't find the exact name I use fuzzy name search. Thats a + * workaround for german umlaute since sometimes there are used in the tags and + * sometimes there are written as ue ae oe. + * + * 3. Step: Do the same like in step 2 once again. This is used to support at least + * one level of recursion in is_in relations. + * + * If there is still no info found I use brute force and use the information from the + * next city. Has to be used for countries with poor is_in tagging. + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.build; + +import java.util.Collection; +import uk.me.parabola.mkgmap.general.MapPoint; +import uk.me.parabola.mkgmap.general.MapPointFastFindMap; +import uk.me.parabola.mkgmap.general.MapPointMultiMap; + +import java.util.Vector; + +public class Locator { + + private final MapPointFastFindMap cityMap = new MapPointFastFindMap(); + private final MapPointMultiMap fuzzyCityMap = new MapPointMultiMap(); + private final java.util.Vector<MapPoint> placesMap = new Vector<MapPoint>(); + + private LocatorConfig locConfig = new LocatorConfig(); + + static double totalTime = 0; + static long totalFinds = 0; + private int autoFillLevel = 0; + + public void addLocation(MapPoint p) + { + resolveIsInInfo(p); // Preprocess the is_in field + + if(autoFillLevel < 1 && p.getCity() == null) + { + // Without autofill city name is the name of the tag + p.setCity(p.getName()); + } + + if(p.getCity() != null) + { + cityMap.put(p.getCity(), p); + + fuzzyCityMap.put(fuzzyDecode(p.getCity()),p); + + if(p.getName() != null && p.getCity().equals(p.getName()) == false) // Name variants ? + fuzzyCityMap.put(fuzzyDecode(p.getName()),p); + } + else + { + // All other places which do not seam to be a real city has to resolved later + placesMap.add(p); + } + + } + + + + public void setAutoFillLevel(int level) + { + autoFillLevel = level; + } + + public void setDefaultCountry(String country, String abbr) + { + locConfig.setDefaultCountry(country, abbr); + } + + public String fixCountryString(String country) + { + return locConfig.fixCountryString(country); + } + + private String isCountry(String country) + { + return locConfig.isCountry(country); + } + + public String getCountryCode(String country) + { + return locConfig.getCountryCode(country); + } + + public int getPOIDispFlag(String country) + { + return locConfig.getPoiDispFlag(country); + } + + private boolean isOpenGeoDBCountry(String country) + { + // Countries that have open geo db data in osm + // Right now this are only germany, austria and swizerland + return locConfig.isOpenGeoDBCountry(country); + } + + private boolean isContinent(String continent) + { + return locConfig.isContinent(continent); + } + + + /** + * resolveIsInInfo tries to get country and region info out of the is_in field + * @param p Point to process + */ + private void resolveIsInInfo(MapPoint p) + { + if(p.getCountry() != null) + p.setCountry(fixCountryString(p.getCountry())); + + if(p.getCountry() != null && p.getRegion() != null && p.getCity() == null) + { + p.setCity(p.getName()); + return; + } + + if(p.getIsIn() != null) + { + String cityList[] = p.getIsIn().split(","); + + //System.out.println(p.getIsIn()); + + // is_in content is not well defined so we try our best to get some info out of it + // Format 1 popular in Germany: "County,State,Country,Continent" + + if(cityList.length > 1 && + isContinent(cityList[cityList.length-1])) // Is last a continent ? + { + // The one before contient should be the country + p.setCountry(fixCountryString(cityList[cityList.length-2].trim())); + + // aks the config which info to use for region info + int offset = locConfig.getRegionOffset(p.getCountry()) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[cityList.length-(offset+1)].trim()); + + } + + // Format 2 other way round: "Continent,Country,State,County" + + if(cityList.length > 1 && isContinent(cityList[0])) // Is first a continent ? + { + // The one before contient should be the country + p.setCountry(fixCountryString(cityList[1].trim())); + + int offset = locConfig.getRegionOffset(p.getCountry()) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[offset].trim()); + } + + // Format like this "County,State,Country" + + if(p.getCountry() == null && cityList.length > 0) + { + // I don't like to check for a list of countries but I don't want other stuff in country field + + String countryStr = isCountry(cityList[cityList.length-1]); + + if(countryStr != null) + { + p.setCountry(countryStr); + + int offset = locConfig.getRegionOffset(countryStr) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[cityList.length-(offset+1)].trim()); + } + } + } + + if(p.getCountry() != null && p.getRegion() != null && p.getCity() == null) + { + // In OpenGeoDB Countries I don't want to mess up the info which city is a real independent + // Community in all other countries I just have to do it + + if(isOpenGeoDBCountry(p.getCountry()) == false) + p.setCity(p.getName()); + } + } + + public MapPoint findNextPoint(MapPoint p) + { + long startTime = System.nanoTime(); + + MapPoint nextPoint = null; + + nextPoint = cityMap.findNextPoint(p); + + totalFinds++; + totalTime = totalTime + ((System.nanoTime() - startTime)/1e9); + return nextPoint; + } + + public MapPoint findByCityName(MapPoint p) + { + MapPoint near = null; + Double minDist = Double.MAX_VALUE; + Collection <MapPoint> nextCityList = null; + + if(p.getCity() == null) + return null; + + nextCityList = cityMap.getList(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; + else + return null; + } + + private MapPoint findCity(MapPoint place, boolean fuzzy) + { + MapPoint near = null; + Double minDist = Double.MAX_VALUE; + Collection <MapPoint> nextCityList = null; + + String isIn = place.getIsIn(); + + if(isIn != null) + { + String cityList[] = isIn.split(","); + + // Go through the isIn string and check if we find a city with this name + // Lets hope we find the next bigger city + + for(int i = 0; i < cityList.length; i++) + { + String biggerCityName=cityList[i].trim(); + + + if(fuzzy == false) + nextCityList = cityMap.getList(biggerCityName); + else + nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName)); + + if(nextCityList != null) + { + for (MapPoint nextCity: nextCityList) + { + Double dist = place.getLocation().distance(nextCity.getLocation()); + + if(dist < minDist) + { + minDist = dist; + near = nextCity; + } + } + } + } + + if (autoFillLevel > 3) // Some debug output to find suspicios relations + { + + if(near != null && minDist > 30000) + { + System.out.println("Locator: " + place.getName() + " is far away from " + + near.getName() + " " + (minDist/1000.0) + " km is_in" + place.getIsIn()); + if(nextCityList != null) + System.out.println("Number of cities with this name: " + nextCityList.size()); + } + + //if(near != null && fuzzy) + //{ + // System.out.println("Locator: " + place.getName() + " may belong to " + + // near.getName() + " is_in" + place.getIsIn()); + //} + } + } + + return near; + } + + public void resolve() { + + if(autoFillLevel < 0) + return; // Nothing to do if autofill is fulli disabled + + if(autoFillLevel > 2) + { + System.out.println("\nLocator City Map contains " + cityMap.size() + " entries"); + System.out.println("Locator Places Map contains " + placesMap.size() + " entries"); + } + + int runCount = 0; + int maxRuns = 2; + int unresCount; + + do + { + unresCount=0; + + for (int i = 0; i < placesMap.size(); i++) + { + MapPoint place = placesMap.get(i); + + if(place != null) + { + + // first lets try exact name + + MapPoint near = findCity(place, false); + + + // if this didn't worked try to workaround german umlaute + + if(near == null) + near = findCity(place, true); + + if(autoFillLevel > 3 && near == null && (runCount + 1) == maxRuns) + { + if(place.getIsIn() != null) + System.out.println("Locator: CAN't locate " + place.getName() + " is_in " + place.getIsIn() + + " " + place.getLocation().toOSMURL()); + } + + + if(near != null) + { + place.setCity(near.getCity()); + place.setZip(near.getZip()); + } + else if (autoFillLevel > 1 && (runCount + 1) == maxRuns) + { + // In the last resolve run just take info from the next known city + near = cityMap.findNextPoint(place); + } + + + if(near != null) + { + if(place.getRegion() == null) + place.setRegion(near.getRegion()); + + if(place.getCountry() == null) + place.setCountry(near.getCountry()); + + } + + if(near == null) + unresCount++; + + } + } + + for (int i = 0; i < placesMap.size(); i++) + { + MapPoint place = placesMap.get(i); + + if (place != null) + { + if( place.getCity() != null) + { + cityMap.put(place.getName(),place); + fuzzyCityMap.put(fuzzyDecode(place.getName()),place); + placesMap.set(i, null); + } + else if(autoFillLevel < 2 && (runCount + 1) == maxRuns) + { + place.setCity(place.getName()); + cityMap.put(place.getName(),place); + } + } + } + + runCount++; + + if(autoFillLevel > 2) + System.out.println("Locator City Map contains " + cityMap.size() + + " entries after resolver run " + runCount + " Still unresolved " + unresCount); + + } + while(unresCount > 0 && runCount < maxRuns); + + } + + public void printStat() + { + System.out.println("Locator Find called: " + totalFinds + " time"); + System.out.println("Locator Find time: " + totalTime + " s"); + + cityMap.printStat(); + } + + 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); + } + +} + Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java =================================================================== --- src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -21,12 +21,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.imgfmt.app.lbl.City; import uk.me.parabola.imgfmt.app.lbl.Country; +import uk.me.parabola.imgfmt.app.lbl.Zip; +import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.lbl.LBLFile; import uk.me.parabola.imgfmt.app.lbl.POIRecord; import uk.me.parabola.imgfmt.app.lbl.Region; @@ -83,26 +83,55 @@ private static final int CLEAR_TOP_BITS = (32 - 15); private final java.util.Map<MapPoint,POIRecord> poimap = new HashMap<MapPoint,POIRecord>(); - private final SortedMap<String, Object> sortedCities = new TreeMap<String, Object>(); + private final java.util.Map<MapPoint,City> cityMap = new HashMap<MapPoint,City>(); + private boolean doRoads; + private Locator locator = new Locator(); private Country country; private Region region; - private String countryName = "UNITED KINGDOM"; - private String countryAbbr = "GBR"; + private String countryName = "COUNTRY"; + private String countryAbbr = "ABC"; private String regionName; private String regionAbbr; + private int locationAutofillLevel = 0; + private boolean poiAddresses = true; + private int poiDisplayFlags = 0; public MapBuilder() { regionName = null; } public void config(EnhancedProperties props) { + + String autoFillPar; + countryName = props.getProperty("country-name", countryName); countryAbbr = props.getProperty("country-abbr", countryAbbr); regionName = props.getProperty("region-name", null); regionAbbr = props.getProperty("region-abbr", null); + + if(props.getProperty("no-poi-address", null) != null) + poiAddresses = false; + + autoFillPar = props.getProperty("location-autofill", null); + + if(autoFillPar != null) + { + try + { + locationAutofillLevel = Integer.parseInt(autoFillPar); + } + catch (Exception e) + { + locationAutofillLevel = 1; + } + } + + locator.setAutoFillLevel(locationAutofillLevel); + + } /** @@ -123,6 +152,7 @@ if(regionName != null) region = lblFile.createRegion(country, regionName, regionAbbr); + processCities(map, src); processPOIs(map, src); //preProcessRoads(map, src); processOverviews(map, src); @@ -157,52 +187,211 @@ } /** - * First stage of handling POIs + * Processing of Cities * - * POIs need to be handled first, because we need the offsets - * in the LBL file. + * Fills the city list in lbl block that is required for find by name + * It also builds up information that is required to get address info + * for the POIs * * @param map The map. * @param src The map data. */ - private void processPOIs(Map map, MapDataSource src) { + private void processCities(Map map, MapDataSource src) { LBLFile lbl = map.getLblFile(); - // gpsmapedit doesn't sort the city names so to be - // friendly we generate the city objects in alphabetic - // order - to do that we first build a map from city - // name to the associated MapPoint - we don't want to - // be fooled by duplicate names so suffix the name - // with the object to make it unique + locator.setDefaultCountry(countryName, countryAbbr); + + // collect the names of the cities for (MapPoint p : src.getPoints()) { if(p.isCity() && p.getName() != null) - sortedCities.put(p.getName() + "@" + p, p); + locator.addLocation(p); // Put the city info the map for missing info } - // now loop through the sorted keys and retrieve - // the MapPoint associated with the key - now we - // can create the City object and remember it for later - for (String s : sortedCities.keySet()) { - MapPoint p = (MapPoint)sortedCities.get(s); - City c; - if(region != null) - c = lbl.createCity(region, p.getName()); - else - c = lbl.createCity(country, p.getName()); - sortedCities.put(s, c); + if(locationAutofillLevel > 0) + locator.resolve(); // Try to fill missing information that include search of next city + + for (MapPoint p : src.getPoints()) + { + if(p.isCity() && p.getName() != null) + { + Country thisCountry; + Region thisRegion; + City thisCity; + + String CountryStr = p.getCountry(); + String RegionStr = p.getRegion(); + + if(CountryStr != null) + thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr)); + else + thisCountry = country; + + if(RegionStr != null) + { + thisRegion = lbl.createRegion(thisCountry,RegionStr, null); + } + else + thisRegion = region; + + if(thisRegion != null) + thisCity = lbl.createCity(thisRegion, p.getName(), true); + else + thisCity = lbl.createCity(thisCountry, p.getName(), true); + + cityMap.put(p, thisCity); + } } - // if point has a nearest city, create a POIRecord to - // reference it + } + + private void processPOIs(Map map, MapDataSource src) { + + LBLFile lbl = map.getLblFile(); + long poiAddrCountr = 0; + boolean checkedForPoiDispFlag = false; + boolean doAutofill; + for (MapPoint p : src.getPoints()) { - MapPoint nearestCityPoint = p.getNearestCityPoint(); - if(nearestCityPoint != null && p.getName() != null) { - POIRecord r = lbl.createPOI(p.getName()); - City nearestCity = (City)sortedCities.get(nearestCityPoint.getName() + "@" + nearestCityPoint); - r.setCityIndex(nearestCity.getIndex()); + + if(p.isCity() == false && + (p.isRoadNamePOI() || poiAddresses)) + { + if(locationAutofillLevel > 0 || p.isRoadNamePOI()) + doAutofill = true; + else + doAutofill = false; + + + String CountryStr = p.getCountry(); + String RegionStr = p.getRegion(); + String ZipStr = p.getZip(); + String CityStr = p.getCity(); + boolean guessed = false; + + if(CityStr != null || ZipStr != null ||RegionStr != null || CountryStr != null) + poiAddrCountr++; + + if(CountryStr != null) + CountryStr = locator.fixCountryString(CountryStr); + + if(CountryStr == null || RegionStr == null || (ZipStr == null && CityStr == null)) + { + MapPoint nextCity = locator.findByCityName(p); + + if(doAutofill && nextCity == null) + nextCity = locator.findNextPoint(p); + + if(nextCity != null) + { + guessed = true; + + if (CountryStr == null) CountryStr = nextCity.getCountry(); + if (RegionStr == null) RegionStr = nextCity.getRegion(); + + if(doAutofill) + { + if(ZipStr == null) + { + String CityZipStr = nextCity.getZip(); + + // Ignore list of Zips seperated by ; + + if(CityZipStr != null && CityZipStr.indexOf(',') < 0) + ZipStr = CityZipStr; + } + + if(CityStr == null) CityStr = nextCity.getCity(); + } + + } + } + + + if(CountryStr != null && checkedForPoiDispFlag == false) + { + // Different countries require different address notation + + poiDisplayFlags = locator.getPOIDispFlag(CountryStr); + checkedForPoiDispFlag = true; + } + + + if(p.isRoadNamePOI() && CityStr != null) + { + // If it is road POI add city name and street name into address info + p.setStreet(p.getName()); + p.setName(p.getName() + "/" + CityStr); + } + + POIRecord r = lbl.createPOI(p.getName()); + + if(CityStr != null) + { + Country thisCountry; + Region thisRegion; + City city; + + if(CountryStr != null) + thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr)); + else + thisCountry = country; + + if(RegionStr != null) + thisRegion = lbl.createRegion(thisCountry,RegionStr, null); + else + thisRegion = region; + + if(thisRegion != null) + city = lbl.createCity(thisRegion, CityStr, false); + else + city = lbl.createCity(thisCountry, CityStr, false); + + r.setCity(city); + + } + + if (ZipStr != null) + { + Zip zip = lbl.createZip(ZipStr); + r.setZipIndex(zip.getIndex()); + } + + if(p.getStreet() != null) + { + Label streetName = lbl.newLabel(p.getStreet()); + r.setStreetName(streetName); + } + else if (guessed == true && locationAutofillLevel > 0) + { + Label streetName = lbl.newLabel("FIX MY ADDRESS"); + r.setStreetName(streetName); + } + + if(p.getHouseNumber() != null) + { + if(r.setSimpleStreetNumber(p.getHouseNumber()) == false) + { + Label streetNumber = lbl.newLabel(p.getHouseNumber()); + r.setComplexStreetNumber(streetNumber); + } + } + + if(p.getPhone() != null) + { + if(r.setSimplePhoneNumber(p.getPhone()) == false) + { + Label phoneNumber = lbl.newLabel(p.getPhone()); + r.setComplexPhoneNumber(phoneNumber); + } + } + poimap.put(p, r); } } + + //System.out.println(poiAddrCountr + " POIs have address info"); + lbl.allPOIsDone(); + } /** @@ -369,6 +558,9 @@ // The bounds of the map. map.setBounds(src.getBounds()); + if(poiDisplayFlags != 0) // POI requested alterate address notation + map.setPoiDisplayFlags(poiDisplayFlags); + // You can add anything here. // But there has to be something, otherwise the map does not show up. // @@ -461,7 +653,8 @@ // retrieve the City created earlier for // this point and store the point info // in it - City c = (City)sortedCities.get(name + "@" + point); + City c = (City)cityMap.get(point); + if(pointIndex > 255) { System.err.println("Can't set city point index for " + name + " (too many indexed points in division)\n"); } else { Index: src/uk/me/parabola/mkgmap/build/LocatorConfig.java =================================================================== --- src/uk/me/parabola/mkgmap/build/LocatorConfig.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/LocatorConfig.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * The Locator tries to fill missing country, region, postal coude information + * + * To do so we analyse the is_in information and if this doesn't helps us we + * try to get info from the next known city + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ +package uk.me.parabola.mkgmap.build; + +import org.w3c.dom.*; +import javax.xml.parsers.*; +import java.io.*; + +import java.util.HashMap; + +public class LocatorConfig { + + private final java.util.Map<String,String> variantMap = new HashMap<String,String>(); + private final java.util.Map<String,String> abrMap = new HashMap<String,String>(); + private final java.util.Map<String,Boolean> geoDbMap = new HashMap<String,Boolean>(); + private final java.util.Map<String,Integer> regOffsetMap = new HashMap<String,Integer>(); + private final java.util.Map<String,Integer> poiDispFlagMap = new HashMap<String,Integer>(); + private final java.util.Map<String,Boolean> continentMap = new HashMap<String,Boolean>(); + + + public LocatorConfig() + { + loadConfig("/LocatorConfig.xml"); + } + + private void loadConfig(String fileName) + { + try + { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream inStream; + + try + { + inStream = new FileInputStream("resources/" + fileName); + } + catch (Exception ex) + { + inStream = null; + } + + if(inStream == null) // If not loaded from disk use from jar file + inStream = this.getClass().getResourceAsStream(fileName); + + Document document = builder.parse(inStream); + + Node rootNode = document.getDocumentElement(); + + if(rootNode.getNodeName().equals("locator")) + { + Node cNode = rootNode.getFirstChild(); + + while(cNode != null) + { + if(cNode.getNodeName().equals("continent")) + { + NamedNodeMap attr = cNode.getAttributes(); + Node nameTag = null; + + if(attr != null) + { + nameTag = attr.getNamedItem("name"); + if(nameTag != null) + addContinent(nameTag.getNodeValue()); + } + + } + + if(cNode.getNodeName().equals("country")) + { + NamedNodeMap attr = cNode.getAttributes(); + Node nameTag = null; + + if(attr != null) + { + nameTag = attr.getNamedItem("name"); + + Node abrTag = attr.getNamedItem("abr"); + + if(abrTag != null && nameTag != null) + addAbr(nameTag.getNodeValue(),abrTag.getNodeValue()); + + if(abrTag == null && nameTag != null) + addAbr(nameTag.getNodeValue(),""); + + Node geoTag = attr.getNamedItem("geodb"); + + if(nameTag != null && geoTag != null) + { + if(geoTag.getNodeValue().equals("1")) + addOpenGeoDb(nameTag.getNodeValue()); + } + + Node regionOffsetTag = attr.getNamedItem("regionOffset"); + + if(regionOffsetTag != null && nameTag != null) + { + addRegionOffset(nameTag.getNodeValue(),Integer.parseInt(regionOffsetTag.getNodeValue())); + } + + Node poiDispTag = attr.getNamedItem("poiDispFlag"); + + if(poiDispTag != null && nameTag != null) + { + addPoiDispTag(nameTag.getNodeValue(),Integer.decode(poiDispTag.getNodeValue())); + } + } + + Node cEntryNode = cNode.getFirstChild(); + while(cEntryNode != null) + { + if(cEntryNode.getNodeName().equals("variant")) + { + Node nodeText = cEntryNode.getFirstChild(); + + if(nodeText != null && nameTag != null) + addVariant(nameTag.getNodeValue(), nodeText.getNodeValue()); + + } + cEntryNode = cEntryNode.getNextSibling(); + } + } + + cNode = cNode.getNextSibling(); + } + } + else + { + System.out.println(fileName + "contains invalid root tag " + rootNode.getNodeName()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + //System.out.println("Something is wrong here"); + } + return; + } + + private void addVariant(String country, String variant) + { + String cStr = country.toUpperCase().trim(); + String vStr = variant.toUpperCase().trim(); + + //System.out.println(vStr + " -> " + cStr); + + variantMap.put(vStr,cStr); + } + + private void addAbr(String country, String abr) + { + String cStr = country.toUpperCase().trim(); + String aStr = abr.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + aStr); + + abrMap.put(cStr,aStr); + } + + private void addRegionOffset(String country, Integer offset) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + offset); + + regOffsetMap.put(cStr,offset); + } + + private void addPoiDispTag(String country, Integer flag) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + flag); + + poiDispFlagMap.put(cStr,flag); + } + + private void addOpenGeoDb(String country) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " openGeoDb"); + + geoDbMap.put(cStr,true); + + } + + private void addContinent(String continent) + { + String cStr = continent.toUpperCase().trim(); + + //System.out.println(cStr + " continent"); + + continentMap.put(cStr,true); + + } + + + public void setDefaultCountry(String country, String abbr) + { + addAbr(country, abbr); + } + + public String fixCountryString(String country) + { + String cStr = country.toUpperCase().trim(); + + String fixedString = variantMap.get(cStr); + + if(fixedString != null) + return fixedString; + else + return(cStr); + } + + public String isCountry(String country) + { + String cStr = fixCountryString(country); + + if(getCountryCode(cStr) != null) + return cStr; + else + return null; + + } + + public String getCountryCode(String country) + { + String cStr = country.toUpperCase().trim(); + return abrMap.get(cStr); + } + + public int getRegionOffset(String country) + { + String cStr = country.toUpperCase().trim(); + + Integer regOffset = regOffsetMap.get(cStr); + + if(regOffset != null) + return regOffset; + else + return 1; // Default is 1 the next string after before country + } + + public int getPoiDispFlag(String country) + { + String cStr = country.toUpperCase().trim(); + + Integer flag = poiDispFlagMap.get(cStr); + + if(flag != null) + return flag; + else + return 0; // Default is 1 the next string after before country + } + + public boolean isOpenGeoDBCountry(String country) + { + // Countries that have open geo db data in osm + // Right now this are only germany, austria and swizerland + + String cStr = country.toUpperCase().trim(); + + if(geoDbMap.get(cStr) != null) + return true; + + return false; + } + + public boolean isContinent(String continent) + { + String s = continent.toUpperCase().trim(); + + if(continentMap.get(s) != null) + return(true); + + return false; + } + + + + +} + Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java =================================================================== --- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -253,6 +253,11 @@ shape.setPoints(way.getPoints()); clipper.clipShape(shape, collector); + + GType pointType = nodeRules.resolveType(way); + + if(pointType != null) + shape.setPoiType(pointType.getType()); } private void addPoint(Node node, GType gt) { @@ -271,6 +276,50 @@ ms.setType(gt.getType()); ms.setMinResolution(gt.getMinResolution()); ms.setMaxResolution(gt.getMaxResolution()); + + // Now try to get some address info for POIs + + String city = element.getTag("addr:city"); + String zip = element.getTag("addr:postcode"); + String street = element.getTag("addr:street"); + String houseNumber = element.getTag("addr:housenumber"); + String phone = element.getTag("phone"); + String isIn = element.getTag("is_in"); + String country = element.getTag("is_in:country"); + String region = element.getTag("is_in:county"); + + if(country != null) + country = element.getTag("addr:country"); + + if(zip == null) + zip = element.getTag("openGeoDB:postal_codes"); + + if(city == null) + city = element.getTag("openGeoDB:sort_name"); + + if(city != null) + ms.setCity(city); + + if(zip != null) + ms.setZip(zip); + + if(street != null) + ms.setStreet(street); + + if(houseNumber != null) + ms.setHouseNumber(houseNumber); + + if(isIn != null) + ms.setIsIn(isIn); + + if(phone != null) + ms.setPhone(phone); + + if(country != null) + ms.setCountry(country); + + if(region != null) + ms.setRegion(region); } void addRoad(Way way, GType gt) { Index: src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -362,6 +362,23 @@ } catch (NumberFormatException e) { endLevel = 0; } + } else if (name.equals("ZipCode")) { + elem.setZip(recode(value)); + } else if (name.equals("CityName")) { + elem.setCity(recode(value)); + } else if (name.equals("StreetDesc")) { + elem.setStreet(recode(value)); + } else if (name.equals("HouseNumber")) { + elem.setHouseNumber(recode(value)); + } else if (name.equals("is_in")) { + elem.setIsIn(recode(value)); + } else if (name.equals("Phone")) { + elem.setPhone(recode(value)); + } else if (name.equals("CountryName")) { + elem.setCountry(recode(value)); + } else if (name.equals("RegionName")) { + //System.out.println("RegionName " + value); + elem.setRegion(recode(value)); } else { return false; } Index: src/uk/me/parabola/mkgmap/main/MapMaker.java =================================================================== --- src/uk/me/parabola/mkgmap/main/MapMaker.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/main/MapMaker.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -36,6 +36,8 @@ import uk.me.parabola.mkgmap.general.LoadableMapDataSource; import uk.me.parabola.mkgmap.general.MapLine; import uk.me.parabola.mkgmap.general.MapPoint; +import uk.me.parabola.mkgmap.general.MapShape; +import uk.me.parabola.mkgmap.general.MapPointFastFindMap; import uk.me.parabola.mkgmap.reader.plugin.MapReader; /** @@ -49,6 +51,7 @@ public String makeMap(CommandArgs args, String filename) { try { LoadableMapDataSource src = loadFromFile(args, filename); + makeAreaPOIs(args, src); makeRoadNamePOIS(args, src); return makeMap(args, src); } catch (FormatException e) { @@ -135,6 +138,44 @@ return src; } + private void makeAreaPOIs(CommandArgs args, LoadableMapDataSource src) { + String s = args.getProperties().getProperty("add-pois-to-areas"); + if (s != null) { + + MapPointFastFindMap poiMap = new MapPointFastFindMap(); + + for (MapPoint point : src.getPoints()) + { + if(point.isRoadNamePOI() == false) // Don't put road pois in this list + poiMap.put(null, point); + } + + for (MapShape shape : src.getShapes()) { + String shapeName = shape.getName(); + + int pointType = shape.getPoiType(); + + // only make a point if the shape has a name and we know what type of point to make + if (pointType == 0) + continue; + + // check if there is not already a poi in that shape + + if(poiMap.findPointInShape(shape, pointType) == null) + { + MapPoint newPoint = new MapPoint(); + newPoint.setName(shapeName); + newPoint.setType(pointType); + newPoint.setLocation(shape.getLocation()); // TODO use centriod + src.getPoints().add(newPoint); + log.info("created POI ", shapeName, "from shape"); + } + } + } + + } + + void makeRoadNamePOIS(CommandArgs args, LoadableMapDataSource src) { String rnp = args.getProperties().getProperty("road-name-pois", null); // are road name POIS wanted? @@ -277,26 +318,10 @@ } String name = road.getName(); - MapPoint nearestCity = null; - if(cities != null) { - double shortestDistance = 10000000; - for(MapPoint mp : cities) { - double distance = coord.distance(mp.getLocation()); - if(distance < shortestDistance) { - shortestDistance = distance; - nearestCity = mp; - } - } - } - MapPoint rnp = new MapPoint(); - if(nearestCity != null && nearestCity.getName() != null) { - //rnp.setNearestCityPoint(nearestCity); - name += "/" + nearestCity.getName(); - } - rnp.setName(name); + rnp.setRoadNamePOI(true); rnp.setType(type); rnp.setLocation(coord); return rnp; Index: src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * This is multimap to store city information for the Address Locator + * + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.general; + + +import java.util.Vector; +import java.util.HashMap; +import java.util.Collection; + +public class MapPointMultiMap{ + + private final java.util.Map<String,Vector<MapPoint>> map = new HashMap<String,Vector<MapPoint>>(); + + public MapPoint put(String name, MapPoint p) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list == null){ + + list = new Vector<MapPoint>(); + list.add(p); + map.put(name, list); + } + else + list.add(p); + + return p; + } + + public MapPoint get(String name) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list != null) + return list.elementAt(0); + else + return null; + } + + public Collection<MapPoint> getList(String name) + { + return map.get(name); + } +} \ No newline at end of file Index: src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * This is multimap to store city information for the Address Locator + * tt provides also a fast tile based nearest point search function + * + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.general; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.Vector; +import java.util.List; +import uk.me.parabola.imgfmt.app.Coord; + +public class MapPointFastFindMap{ + + private final java.util.Map<String,Vector<MapPoint>> map = new HashMap<String,Vector<MapPoint>>(); + private final java.util.Map<Long,Vector<MapPoint>> posMap = new HashMap<Long,Vector<MapPoint>>(); + private final java.util.Vector<MapPoint> points = new Vector<MapPoint>(); + + private static final long POS_HASH_DIV = 8000; // the smaller -> more tiles + private static final long POS_HASH_MUL = 10000; // multiplicator for latitude to create hash + + public MapPoint put(String name, MapPoint p) + { + Vector<MapPoint> list; + + if(name != null) + { + list = map.get(name); + + if(list == null){ + + list = new Vector<MapPoint>(); + list.add(p); + map.put(name, list); + } + else + list.add(p); + + points.add(p); + } + + long posHash = getPosHashVal(p.getLocation().getLatitude(), p.getLocation().getLongitude()); + + list = posMap.get(posHash); + + if(list == null) + { + list = new Vector<MapPoint>(); + list.add(p); + posMap.put(posHash, list); + } + else + list.add(p); + + return p; + } + + public MapPoint get(String name) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list != null) + return list.elementAt(0); + else + return null; + } + + public Collection<MapPoint> getList(String name) + { + return map.get(name); + } + + public long size() + { + return points.size(); + } + + public Collection<MapPoint> values() + { + return points; + } + + public MapPoint get(int index) + { + return points.get(index); + } + + public MapPoint set(int index, MapPoint p) + { + return points.set(index, p); + } + + public boolean remove(MapPoint p) + { + return points.remove(p); + } + + + public MapPoint findNextPoint(MapPoint p) + { + /* tile based search + + to prevent expensive linear search over all points we put the points + into tiles. We just search the tiles the point is in linear and the + sourounding tiles. If we don't find a point we have to search further + arround the central tile + + */ + + Vector<MapPoint> list; + double minDist = Double.MAX_VALUE; + MapPoint nextPoint = null; + + if(posMap.size() < 1) // No point in list + return nextPoint; + + long centLatitIdx = p.getLocation().getLatitude() / POS_HASH_DIV ; + long centLongiIdx = p.getLocation().getLongitude() / POS_HASH_DIV ; + long delta = 1; + + long latitIdx; + long longiIdx; + long posHash; + + do + { + // in the first step we only check our tile and the tiles sourinding us + + for(latitIdx = centLatitIdx - delta; latitIdx <= centLatitIdx + delta; latitIdx++) + for(longiIdx = centLongiIdx - delta; longiIdx <= centLongiIdx + delta; longiIdx++) + { + if(delta < 2 + || latitIdx == centLatitIdx - delta + || latitIdx == centLatitIdx + delta + || longiIdx == centLongiIdx - delta + || longiIdx == centLongiIdx + delta) + { + + posHash = latitIdx * POS_HASH_MUL + longiIdx; + + list = posMap.get(posHash); + + if(list != null) + { + + for (MapPoint actPoint: list) + { + double distance = actPoint.getLocation().distance(p.getLocation()); + + if(distance < minDist) + { + nextPoint = actPoint; + minDist = distance; + + } + } + } + } + } + delta ++; // We have to look in tiles farer away + } + while(nextPoint == null); + + return nextPoint; + } + + public MapPoint findPointInShape(MapShape shape, int pointType) + { + Vector<MapPoint> list; + List<Coord> points = shape.getPoints(); + MapPoint nextPoint = null; + long lastHashValue = -1; + long posHash; + + if(posMap.size() < 1) // No point in list + return nextPoint; + + for(int i=0; i < points.size(); i++) + { + posHash = getPosHashVal(points.get(i).getLatitude(),points.get(i).getLongitude()); + + if(posHash == lastHashValue) // Have we already checked this tile ? + continue; + + lastHashValue = posHash; + + list = posMap.get(posHash); + + if(list != null) + { + for (MapPoint actPoint: list) + { + if(pointType == 0 || actPoint.getType() == pointType) + { + if(shape.contains( actPoint.getLocation())) + return actPoint; + } + } + } + } + + return null; + } + + private long getPosHashVal(long lat, long lon) + { + long latitIdx = lat / POS_HASH_DIV ; + long longiIdx = lon / POS_HASH_DIV ; + + //System.out.println("LatIdx " + latitIdx + " LonIdx " + longiIdx); + + return latitIdx * POS_HASH_MUL + longiIdx; + } + + public void printStat() + { + System.out.println("Locator PosHashmap contains " + posMap.size() + " tiles"); + } +} \ No newline at end of file Index: src/uk/me/parabola/mkgmap/general/MapElement.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapElement.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapElement.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -15,6 +15,9 @@ */ package uk.me.parabola.mkgmap.general; +import java.util.Map; +import java.util.HashMap; + import uk.me.parabola.imgfmt.app.Coord; /** @@ -29,6 +32,13 @@ private int minResolution = 24; private int maxResolution = 24; + + private String zipCode; + private String city; + private String region; + private String country; + + private final Map<String, String> attributes = new HashMap<String, String>(); protected MapElement() { } @@ -53,9 +63,84 @@ } public void setName(String name) { - this.name = name; + if(name != null) + this.name = name.toUpperCase(); } + public String getCity() { + return city; + } + + public void setCity(String city) { + if(city != null) + this.city = city.toUpperCase(); + } + + public String getZip() { + return zipCode; + } + + public void setZip(String zip) { + this.zipCode = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + if(country != null) + this.country = country.toUpperCase(); + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + if(region != null) + this.region = region.toUpperCase(); + } + + public String getStreet() { + return attributes.get("street"); + } + + public void setStreet(String street) { + attributes.put("street", street); + } + + public String getPhone() { + return attributes.get("phone"); + } + + public void setPhone(String phone) { + + if(phone.startsWith("00")) + { + phone = phone.replaceFirst("00","+"); + } + attributes.put("phone", phone); + } + + public String getHouseNumber() { + return attributes.get("houseNumber"); + } + + public void setHouseNumber(String houseNumber) { + attributes.put("houseNumber", houseNumber); + } + + public String getIsIn() { + return attributes.get("isIn"); + } + + public void setIsIn(String isIn) { + if(isIn != null) + attributes.put("isIn", isIn.toUpperCase()); + } + + /** * This is the type code that goes in the .img file so that the GPS device * knows what to display. Index: src/uk/me/parabola/mkgmap/general/MapPoint.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPoint.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapPoint.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -27,6 +27,7 @@ public class MapPoint extends MapElement { private Coord location; private MapPoint nearestCityPoint; + private boolean isRoadNamePoi = false; public MapPoint() { } @@ -63,11 +64,11 @@ return type >= 0x0100 && type <= 0x1100; } - public void setNearestCityPoint(MapPoint nearestCityPoint) { - this.nearestCityPoint = nearestCityPoint; + public void setRoadNamePOI(boolean isRoadNamePoi) { + this.isRoadNamePoi = isRoadNamePoi; } - public MapPoint getNearestCityPoint() { - return nearestCityPoint; + public boolean isRoadNamePOI() { + return this.isRoadNamePoi; } } Index: src/uk/me/parabola/mkgmap/general/MapShape.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapShape.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapShape.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -15,6 +15,11 @@ */ package uk.me.parabola.mkgmap.general; +import java.util.ArrayList; +import java.util.List; + +import uk.me.parabola.imgfmt.app.Coord; + /** * A shape or polygon is just the same as a line really as far as I can tell. * There are some things that you cannot do with them semantically. @@ -23,6 +28,8 @@ */ public class MapShape extends MapLine {// So top code can link objects from here + private int poiType = 0; + public MapShape() { } @@ -38,5 +45,158 @@ throw new IllegalArgumentException( "can't set a direction on a polygon"); } + + public void setPoiType(int type) + { + this.poiType = type; + } + + public int getPoiType() + { + return this.poiType; + } + + /** + * Checks if a point is contained within this shape. Points on the + * edge of the shape are considered inside. + * + * @param co point to check + * @return true if point is in shape, false otherwise + */ + public boolean contains(Coord co) { + return contains(this.getPoints(), co, true); + } + + /* + * Checks if a point is contained within a shape. + * + * @param points points that define the shape + * @param target point to check + * @param onLineIsInside if a point on the line should be considered inside the shape + * @return true if point is contained within the shape, false if the target point is outside the shape + */ + private static boolean contains(List<Coord> points, Coord target, boolean onLineIsInside) { + // implementation of the Ray casting algorithm as described here: + // http://en.wikipedia.org/wiki/Point_in_polygon + // with inspiration from: + // http://www.visibone.com/inpoly/ + boolean inside = false; + if (points.size() < 3) + return false; + // complete the shape if we're dealing with a MapShape that is not closed + Coord start = points.get(0); + Coord end = points.get(points.size() - 1); + if (!start.equals(end)) { + // make copy of the shape's geometry + List<Coord> pointsTemp = new ArrayList<Coord>(points.size() + 1); + for (Coord coord : points) { + pointsTemp.add(new Coord(coord.getLatitude(), coord.getLongitude())); + } + pointsTemp.add(new Coord(start.getLatitude(), start.getLongitude())); + points = pointsTemp; + } + + int xtarget = target.getLatitude(); + int ytarget = target.getLongitude(); + + for (int i = 0; i < points.size() - 1; i++) { + + // apply transformation points to change target point to (0,0) + int x0 = points.get(i).getLatitude() - xtarget; + int y0 = points.get(i).getLongitude() - ytarget; + int x1 = points.get(i+1).getLatitude() - xtarget; + int y1 = points.get(i+1).getLongitude() - ytarget; + + // ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily + if (x0 > x1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + + // use (0,0) as target because points already transformed + if (isPointOnLine(x0, y0, x1, y1, 0, 0)) + return onLineIsInside; + + // explanation of if statement + // + // (x0 < 0 && x1 >= 0): + // are the x values between the y axis? only include points from the right + // with this check so that corners aren't counted twice + // + // (y0 * (x1 - x0) > (y1 - y0) * x0): + // from y = mx + b: + // => b = y0 ((y1 - y0) / (x1 - x0)) * x0 + // for intersection, b > 0 + // from y = mx + b, b = y - mx + // => y - mx > 0 + // => y0 - ((y1 - y0) / (x1 - x0)) * x0 > 0 + // => y0 > ((y1 - y0) / (x1 - x0)) * x0 + // from 'if (x0 > x1)', x1 >= x0 + // => x1 - x0 >=0 + // => y0 * (x1 - x0) > (y1 - y0) * x0 + if ((x0 < 0 && x1 >= 0) && (y0 * (x1 - x0)) > ((y1 - y0) * x0)) + inside = !inside; + } + + return inside; + } + + /* + * Checks if a point is on a line. + * + * @param x0 x value of first point in line + * @param y0 y value of first point in line + * @param x1 x value of second point in line + * @param y1 y value of second point in line + * @param xt x value of target point + * @param yt y value of target point + * @return return true if point is on the line, false if the point isn't on the line + */ + private static boolean isPointOnLine(int x0, int y0, int x1, int y1, int xt, int yt) { + // this implementation avoids using doubles + // apply transformation points to change target point to (0,0) + x0 = x0 - xt; + y0 = y0 - yt; + x1 = x1 - xt; + y1 = y1 - yt; + + // ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily + if (x0 > x1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + + // if a point is on the edge of shape (on a line), it's considered outside the shape + // special case if line is on y-axis + if (x0 == 0 && x1 == 0) { + // ensure that y0 is smaller than y1 so that we can just check if the line intersects the x axis + if (y0 > y1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + // test to see if we have a vertical line touches x-axis + if (y0 <= 0 && y1 >= 0) + return true; + // checks if point is on the line, see comments in contain() for derivation of similar + // formula - left as an exercise to the reader ;) + } else if ((x0 <= 0 && x1 >= 0) && (y0 * (x1 - x0)) == ((y1 - y0) * x0)) { + return true; + } + return false; + } + + } Index: build.xml =================================================================== --- build.xml (.../upstream/mkgmap) (revision 280) +++ build.xml (.../work_poiaddr_area/mkgmap) (revision 280) @@ -78,8 +78,9 @@ <target name="build" depends="compile" > <copy todir="${build.classes}"> <fileset dir="${resources}"> - <include name="*.csv"/> + <include name="*.csv"/> <include name="*.properties"/> + <include name="*.xml"/> <include name="**/*.trans"/> <include name="styles/**"/> <include name="help/**"/> @@ -151,6 +152,7 @@ manifest="${resources}/MANIFEST.MF"> <include name="**/*.class"/> <include name="*.csv"/> + <include name="*.xml"/> <include name="*.properties"/> <include name="**/*.trans"/> <include name="styles/**"/> Index: resources/styles/default/polygons =================================================================== --- resources/styles/default/polygons (.../upstream/mkgmap) (revision 280) +++ resources/styles/default/polygons (.../work_poiaddr_area/mkgmap) (revision 280) @@ -6,6 +6,8 @@ amenity=supermarket [0x08 resolution 21] amenity=university [0x0a resolution 18] +building=yes [0x13 resolution 18] + landuse=allotments [0x4e resolution 20] landuse=cemetary [0x1a resolution 18] landuse=cemetery [0x1a resolution 18] Index: resources/LocatorConfig.xml =================================================================== --- resources/LocatorConfig.xml (.../upstream/mkgmap) (revision 0) +++ resources/LocatorConfig.xml (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<locator> + <country name="Deutschland" abr="DEU" geodb="1" regionOffset="3" poiDispFlag="0xc"> + <variant>Bundesrepublik Deutschland</variant> + <variant>Germany</variant> + <variant>DE</variant> + </country> + <country name="Österreich" abr="AUT" geodb="1" poiDispFlag="0xc"> + <variant>Austria</variant> + <variant>AT</variant> + </country> + <country name="Schweiz" abr="CHE" geodb="1" poiDispFlag="0xc"> + <variant>Switzerland</variant> + <variant>CH</variant> + </country> + <country name="United Kingdom" abr="GBR"> + <variant>UK</variant> + <variant>GB</variant> + </country> + <country name="Italia" abr="ITA" regionOffset="2"> + <variant>Italy</variant> + <variant>IT</variant> + </country> + <country name="France" abr="FRA"> + </country> + <continent name="Europe"> + </continent> + <continent name="Africa"> + </continent> + <continent name="Asia"> + </continent> + <continent name="North America"> + </continent> + <continent name="South America"> + </continent> + <continent name="Oceania"> + </continent> +</locator> Index: resources/help/en/options =================================================================== --- resources/help/en/options (.../upstream/mkgmap) (revision 280) +++ resources/help/en/options (.../work_poiaddr_area/mkgmap) (revision 280) @@ -120,6 +120,11 @@ Generate a POI for each named road. By default, the POIs' Garmin type code is 0x640a. If desired, a different type code can be specified with this option. + +--add-pois-to-areas + Generate a POI for each area. The POIs are created after the style + is applied and only for polygon types that have a reasonable point + equivalent. --tdbfile Write a .tdb file. @@ -134,6 +139,24 @@ same area, you can see through this map and see the lower map too. Useful for contour line maps among other things. +--no-poi-address + Disable address / phone information to POIs. Address info is read according to + the "Karlsruhe" tagging schema. Automatic filling of missing information could + be enabled using the "location-autofill" option. + +--location-autofill=''number'' + Controls how country region info is gathered for cities / streets and pois + + 0 (Default) The country region info is gathered by analysis of the cities is_in tags. + If no country region info is present the default passed default country region is used. + + 1 Additional analysis of partial is_in info to get relations between hamlets and cities + + 2 Brute force search for nearest city with info if all methods before failed. Warning + cities my end up in the wrong country/region. + + 3 Enables debug output about suspicious relations that might cause wrong country region info + --version Output program version.

Hi Berni, Thanks for picking up my patch and for the improvements! :-) I noticed that it was just committed to the poi branch so I'll give it test and report back when I have some free time on Monday. Cheers, Ben On Sat, Feb 28, 2009 at 2:39 PM, Bernhard Heibler <bernhard@heibler.de> wrote:
Hi,
I have integrated Ben Konrath's Area POI patch into my code. I did the following changes to Ben's code:
- Replaced the hard coded area to poi type mapping with the build in rule sets. Shapes are checked against the point rules to add missing pois. - Replaced the linear search (to avoid duplicated pois) with tile based search - Add buildings to polygons style - Moved my helper classes to general directory
You have to add the --add-pois-to-areas to enable the Area POIs generation. I'm not sure if we should generate a new rule set in the style folder for this purpose. It works pretty good using the point rules but it might be confusing.
Thanks Ben for providing the ray cast code to check for points in polygons.
Thanks Berni.
Index: test/uk/me/parabola/mkgmap/general/PointInShapeTest.java =================================================================== --- test/uk/me/parabola/mkgmap/general/PointInShapeTest.java (.../upstream/mkgmap) (revision 0) +++ test/uk/me/parabola/mkgmap/general/PointInShapeTest.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,289 @@ +/** + * + */ +package uk.me.parabola.mkgmap.general; + + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import uk.me.parabola.imgfmt.app.Coord; + +/** + * @author ben + * + */ +public class PointInShapeTest { + + MapShape square, triangle, line; + int squareSize = 4; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Square + List<Coord> points = Arrays.asList( + new Coord(0, 0), + new Coord(0, squareSize), + new Coord(squareSize, squareSize), + new Coord(squareSize, 0), + new Coord(0,0) + ); + square = new MapShape(); + square.setPoints(points); + + // Triangle + points = Arrays.asList( + new Coord(0,0), + new Coord(4,4), + new Coord(8,0), + new Coord(0,0) + ); + triangle = new MapShape(); + triangle.setPoints(points); + + // Line + points = Arrays.asList( + new Coord(2,5), + new Coord(12,1) + ); + line = new MapShape(); + line.setPoints(points); + } + + @Test + public void testLinePointsInsideSquare() { + + // inside square, 1 unit from corners + List<Coord> points = Arrays.asList( + new Coord(1, squareSize/2), + new Coord(squareSize/2, squareSize - 1), + new Coord(squareSize - 1, squareSize/2), + new Coord(squareSize/2, 1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside square", + square.contains(coord)); + } + + // on the line + points = Arrays.asList( + new Coord(0, squareSize/2), + new Coord(squareSize/2, squareSize), + new Coord(squareSize, squareSize/2), + new Coord(squareSize/2, 0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square", + square.contains(coord)); + } + } + + @Test + public void testLinePointsOutsideSquare() { + + // outside square, 1 unit from line + List<Coord> points = Arrays.asList( + new Coord(-1, squareSize/2), + new Coord(squareSize/2, squareSize + 1), + new Coord(squareSize + 1, squareSize/2), + new Coord(squareSize/2, -1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square", + square.contains(coord)); + } + } + + @Test + public void testCornerPointsInsideSquare() { + // corner points + for (Coord cornerpoint : square.getPoints()) { + Coord co = new Coord(cornerpoint.getLatitude(), cornerpoint.getLongitude()); + assertTrue("corner point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside square", + square.contains(co)); + } + + // sub shape + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1; + int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be inside square", square.contains(co)); + } + + // tests above / below corner points, on the outside edge + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude(); + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // tests to the right / left side of corner points, on square edge + for (Coord cornerpoint : square.getPoints()) { + int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude(); + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + } + + @Test + public void testCornerPointsOutsideSquare() { + + // tests above / below corner points, outside squate + for (Coord cornerpoint : square.getPoints()) { + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude(); + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // tests to the right / left side of corner points, outside square + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude(); + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // super shape + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + } + + + @Test + public void testLinePointsInsideTriangle() { + // inside triangle, above / below lines + List<Coord> points = Arrays.asList( + new Coord(2,1), + new Coord(6,1), + new Coord(4,1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", + triangle.contains(coord)); + } + + // on lines + points = Arrays.asList( + new Coord(2,2), + new Coord(6,2), + new Coord(4,0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testLinePointsOutsideTriangle() { + // outside triangle, above / below lines + List<Coord> points = Arrays.asList( + new Coord(2,3), + new Coord(6,3), + new Coord(4,-1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testCornerPointsInsideTriangle() { + // corner points + for (Coord cornerpoint : triangle.getPoints()) { + assertTrue("point (" + cornerpoint.getLatitude() + ", " + cornerpoint.getLongitude() + ") should be outside triangle", + triangle.contains(cornerpoint)); + } + + // sub shape + List<Coord> points = Arrays.asList( + new Coord(2,1), + new Coord(4,3), + new Coord(6,1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", + triangle.contains(coord)); + } + + // beside points, on edge + points = Arrays.asList( + new Coord(1,0), + new Coord(7,0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testCornerPointsOutsideTriangle() { + // above points + for (Coord coord : triangle.getPoints()) { + Coord co = new Coord(coord.getLatitude(), coord.getLongitude() + 1); + assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside triangle", + triangle.contains(co)); + } + + // outside triangle, beside / below lines + List<Coord> points = Arrays.asList( + new Coord(-1,0), + new Coord(0,-1), + new Coord(3,4), + new Coord(5,4), + new Coord(9,0), + new Coord(8,-1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + + // super shape + for (Coord cornerpoint : triangle.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside triangle", triangle.contains(co)); + } + + } + + @Test + public void testLine() { + // midpoint + Coord co = new Coord(7,3); + assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside line", + line.contains(co)); + + } +} Index: src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -236,6 +236,10 @@ public void setMapId(int id) { mapId = id; } + + public void setPoiDisplayFlags(byte poiDisplayFlags) { + this.poiDisplayFlags = poiDisplayFlags; + }
public int getMapInfoSize() { return mapInfoSize; Index: src/uk/me/parabola/imgfmt/app/trergn/TREFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -315,6 +315,10 @@ header.setBounds(area); }
+ public void setPoiDisplayFlags(byte b) { + header.setPoiDisplayFlags(b); + } + public String[] getCopyrights() { if (!isReadable()) throw new IllegalStateException("not open for reading"); Index: src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -16,6 +16,7 @@ */ package uk.me.parabola.imgfmt.app.lbl;
+import java.util.Vector; import uk.me.parabola.imgfmt.app.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label;
@@ -23,6 +24,94 @@ * @author Steve Ratcliffe */ public class POIRecord { + + class SimpleStreetPhoneNumber // Helper Class to encode Street Phone Numbers + { + /** + Street and Phone numbers can be stored in two different ways in the poi record + Simple Number that only contain digits are coded in base 11 coding. + This helper class tries to code the given number. If the number contains other + chars like in 4a the coding fails and the caller has to use a Label instead + */ + + private byte encodedNumber[] = null; + private int encodedSize = 0; + + public boolean set(String number) + { + int i = 0; + int j = 0; + int val = 0; + + // remove sourounding whitespaces to increase chance for simple encoding + + number = number.trim(); + + encodedNumber = new byte[(number.length()/2)+2]; + + while(i < number.length()) + { + int c1 = 0; + int c2 = 0; + + c1 = decodeChar(number.charAt(i)); + i++; + + if(i < number.length()) + { + c2 = decodeChar(number.charAt(i)); + i++; + } + else + c2 = 10; + + if(c1 < 0 || c1 > 10 || c2 < 0 || c2 > 10) // Only 0-9 and - allowed + { + return false; + } + + val = c1 * 11 + c2; // Encode as base 11 + + if(i < 3 || i >= number.length()) // first byte needs special marking with 0x80 + val = val | 0x80; // If this is not set would be treated as label pointer + + encodedNumber[j++] = (byte)val; + } + + if((val & 0x80) == 0 || i < 3) // terminate string with 0x80 if not done + { + val = 0xF8; + encodedNumber[j++] = (byte)val; + } + + encodedSize = j; + + return true; + } + + public void write(ImgFileWriter writer) + { + for(int i = 0; i < encodedSize; i++) + writer.put(encodedNumber[i]); + } + + public boolean isUsed() + { + return (encodedSize > 0); + } + + public int getSize() + { + return encodedSize; + } + + private int decodeChar(char ch) + { + return (ch - '0'); + } + + } + public static final byte HAS_STREET_NUM = 0x01; public static final byte HAS_STREET = 0x02; public static final byte HAS_CITY = 0x04; @@ -43,13 +132,16 @@ private int offset = -1; private Label poiName;
- private int streetNumber; + private SimpleStreetPhoneNumber simpleStreetNumber = new SimpleStreetPhoneNumber(); + private SimpleStreetPhoneNumber simplePhoneNumber = new SimpleStreetPhoneNumber(); + private Label streetName; private Label streetNumberName; // Used for numbers such as 221b + private Label complexPhoneNumber; // Used for numbers such as 221b + + private City city = null; + private char zipIndex = 0;
- private char cityIndex ; - private char zipIndex ; - private String phoneNumber;
public void setLabel(Label label) { @@ -60,14 +152,35 @@ this.streetName = label; }
+ public boolean setSimpleStreetNumber(String streetNumber) + { + return simpleStreetNumber.set(streetNumber); + } + + public void setComplexStreetNumber(Label label) + { + streetNumberName = label; + } + + public boolean setSimplePhoneNumber(String phone) + { + return simplePhoneNumber.set(phone); + } + + public void setComplexPhoneNumber(Label label) + { + complexPhoneNumber = label; + } + + public void setZipIndex(int zipIndex) { this.zipIndex = (char) zipIndex; }
- public void setCityIndex(int cityIndex) + public void setCity(City city) { - this.cityIndex = (char) cityIndex; + this.city = city; }
void write(ImgFileWriter writer, byte POIGlobalFlags, int realofs, @@ -82,11 +195,21 @@ if (POIGlobalFlags != getPOIFlags()) writer.put(getWrittenPOIFlags(POIGlobalFlags));
+ if (streetNumberName != null) + { + int labOff = streetNumberName.getOffset(); + writer.put((byte)((labOff & 0x7F0000) >> 16)); + writer.putChar((char)(labOff & 0xFFFF)); + } + else if (simpleStreetNumber.isUsed()) + simpleStreetNumber.write(writer); + if (streetName != null) writer.put3(streetName.getOffset());
- if (cityIndex > 0) + if (city != null) { + char cityIndex = (char) city.getIndex(); if(numCities > 255) writer.putChar(cityIndex); else @@ -100,44 +223,60 @@ else writer.put((byte)zipIndex); } + + if (complexPhoneNumber != null) + { + int labOff = complexPhoneNumber.getOffset(); + writer.put((byte)((labOff & 0x7F0000) >> 16)); + writer.putChar((char)(labOff & 0xFFFF)); + } + else if (simplePhoneNumber.isUsed()) + simplePhoneNumber.write(writer); }
byte getPOIFlags() { byte b = 0; if (streetName != null) b |= HAS_STREET; - if (cityIndex > 0) + if (simpleStreetNumber.isUsed() || streetNumberName != null) + b |= HAS_STREET_NUM; + if (city != null) b |= HAS_CITY; if (zipIndex > 0) - b |= HAS_ZIP; + b |= HAS_ZIP; + if (simplePhoneNumber.isUsed() || complexPhoneNumber != null) + b |= HAS_PHONE; return b; }
byte getWrittenPOIFlags(byte POIGlobalFlags) { - int mask; - int flag = 0; - int j = 0; + int mask; + int flag = 0; + int j = 0;
- int usedFields = getPOIFlags(); + int usedFields = getPOIFlags();
- /* the local POI flag is really tricky - if a bit is not set in the global mask - we have to skip this bit in the local mask. - In other words the meaning of the local bits - change influenced by the global bits */ + /* the local POI flag is really tricky if a bit is not set in the global mask + we have to skip this bit in the local mask. In other words the meaning of the local bits + change influenced by the global bits */ + + for(byte i = 0; i < 6; i++) + { + mask = 1 << i;
- for (byte i = 0; i < 6; i++) { - mask = 1 << i; - - if ((mask & POIGlobalFlags) == mask) { - if ((mask & usedFields) == mask) - flag |= (1 << j); - j++; + if((mask & POIGlobalFlags) == mask) + { + if((mask & usedFields) == mask) + flag = flag | (1 << j); + j++; + } + }
- } - return (byte) flag; + flag = flag | 0x80; // gpsmapedit asserts for this bit set + + return (byte) flag; }
/** @@ -150,9 +289,17 @@ int size = 3; if (POIGlobalFlags != getPOIFlags()) size += 1; + if (simpleStreetNumber.isUsed()) + size += simpleStreetNumber.getSize(); + if (streetNumberName != null) + size += 3; + if (simplePhoneNumber.isUsed()) + size += simplePhoneNumber.getSize(); + if (complexPhoneNumber != null) + size += 3; if (streetName != null) - size += 3; - if (cityIndex > 0) + size += 3; + if (city != null) { /* depending on how many cities are in the LBL block we have @@ -160,9 +307,9 @@ */
if(numCities > 255) - size += 2; + size += 2; else - size += 1; + size += 1; } if (zipIndex > 0) { Index: src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -20,6 +20,8 @@ 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.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label; @@ -33,7 +35,8 @@ public class PlacesFile { private final Map<String, Country> countries = new LinkedHashMap<String, Country>(); private final Map<String, Region> regions = new LinkedHashMap<String, Region>(); - private final List<City> cities = new ArrayList<City>(); + private final Map<String, City> cities = new LinkedHashMap<String, City>(); + private final SortedMap<String, City> cityList = new TreeMap<String, City>(); private final Map<String, Zip> postalCodes = new LinkedHashMap<String, Zip>(); private final List<POIRecord> pois = new ArrayList<POIRecord>();
@@ -62,8 +65,12 @@ r.write(writer); placeHeader.endRegions(writer.position());
- for (City c : cities) + for (String s : cityList.keySet()) + { + City c = cityList.get(s); c.write(writer); + } + placeHeader.endCity(writer.position());
int poistart = writer.position(); @@ -79,56 +86,122 @@ }
Country createCountry(String name, String abbr) { - Country c = new Country(countries.size()+1); - + String s = abbr != null ? name + (char)0x1d + abbr : name; + + Country c = countries.get(s); + + if(c == null) + { + c = new Country(countries.size()+1);
- Label l = lblFile.newLabel(s); - c.setLabel(l); - - countries.put(name, c); + Label l = lblFile.newLabel(s); + c.setLabel(l); + countries.put(s, c); + } return c; }
Region createRegion(Country country, String name, String abbr) { - Region r = new Region(country, regions.size()+1); - + String s = abbr != null ? name + (char)0x1d + abbr : name;
- Label l = lblFile.newLabel(s); - r.setLabel(l); - - regions.put(name, r); + String uniqueRegionName = s.toUpperCase().concat(Long.toString(country.getIndex())); + + Region r = regions.get(uniqueRegionName); + + if(r == null) + { + r = new Region(country, regions.size()+1); + Label l = lblFile.newLabel(s); + r.setLabel(l); + regions.put(uniqueRegionName, r); + } return r; }
- City createCity(Country country, String name) { - City c = new City(country, cities.size()+1); + City createCity(Country country, String name, boolean unique) { + + String uniqueCityName = name.toUpperCase().concat("_C").concat(Long.toString(country.getIndex())); + + City c = null;
- Label l = lblFile.newLabel(name); - c.setLabel(l); // label may be ignored if pointref is set + if(!unique) + c = cities.get(uniqueCityName); + + if(c == null) + { + c = new City(country);
- cities.add(c); + Label l = lblFile.newLabel(name); + c.setLabel(l); + + /* + Adding 0 in between is important to get right sort order !!! + We have to make sure that "Kirchdorf" gets sorted before "Kirchdorf am Inn" + If this order is not correct nuvi would not find right city + */ + + cityList.put(name + " 0" + c, c); + cities.put(uniqueCityName, c); + } + return c; - } + }
- City createCity(Region region, String name) { - City c = new City(region, cities.size()+1); + City createCity(Region region, String name, boolean unique) { + + String uniqueCityName = name.toUpperCase().concat("_R").concat(Long.toString(region.getIndex())); + + City c = null;
- Label l = lblFile.newLabel(name); - c.setLabel(l); // label may be ignored if pointref is set + if(!unique) + c = cities.get(uniqueCityName); + + if(c == null) + { + c = new City(region);
- cities.add(c); + Label l = lblFile.newLabel(name); + c.setLabel(l); + + /* + Adding 0 in between is important to get right sort order !!! + We have to make sure that "Kirchdorf" gets sorted before "Kirchdorf am Inn" + If this order is not correct nuvi would not find right city + */ + + cityList.put(name + " 0" + c, c); + cities.put(uniqueCityName, c); + } + return c; }
+ private void sortCities() + { + int index = 1; + + for (String s : cityList.keySet()) + { + City c = cityList.get(s); + c.setIndex(index++); + } + } + Zip createZip(String code) { - Zip z = new Zip(postalCodes.size()+1); + + Zip z = postalCodes.get(code); + + if(z == null) + { + z = new Zip(postalCodes.size()+1);
- Label l = lblFile.newLabel(code); - z.setLabel(l); + Label l = lblFile.newLabel(code); + z.setLabel(l);
- postalCodes.put(code, z); + postalCodes.put(code, z); + } return z; }
@@ -146,6 +219,9 @@ }
void allPOIsDone() { + + sortCities(); + poisClosed = true;
byte poiFlags = 0; Index: src/uk/me/parabola/imgfmt/app/lbl/City.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/City.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/City.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -30,7 +30,7 @@ private static final int POINT_REF = 0x8000; private static final int REGION_IS_COUNTRY = 0x4000;
- private final int index; + private int index = -1;
private final Region region; private final Country country; @@ -49,13 +49,13 @@ // null if the location is being specified. private Label label;
- public City(Region region, int index) { + public City(Region region) { this.region = region; this.country = null; this.index = index; }
- public City(Country country, int index) { + public City(Country country) { this.country = country; this.region = null; this.index = index; @@ -83,9 +83,15 @@ }
public int getIndex() { + if (index == -1) + throw new IllegalStateException("Offset not known yet."); return index; }
+ public void setIndex(int index) { + this.index = index; + } + public void setLabel(Label label) { pointRef = false; this.label = label; Index: src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -147,14 +147,18 @@ return places.createRegion(country, region, abbr); }
- public City createCity(Region region, String city) { - return places.createCity(region, city); + public City createCity(Region region, String city, boolean unique) { + return places.createCity(region, city, unique); }
- public City createCity(Country country, String city) { - return places.createCity(country, city); + public City createCity(Country country, String city, boolean unique) { + return places.createCity(country, city, unique); }
+ public Zip createZip(String code) { + return places.createZip(code); + } + public void allPOIsDone() { places.allPOIsDone(); } Index: src/uk/me/parabola/imgfmt/app/map/Map.java =================================================================== --- src/uk/me/parabola/imgfmt/app/map/Map.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/map/Map.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -225,6 +225,14 @@ treFile.addPolygonOverview(ov); }
+ /** + * Set the point of interest flags. + * @param flags The POI flags. + */ + public void setPoiDisplayFlags(int flags) { + treFile.setPoiDisplayFlags((byte) flags); + } + public void addMapObject(MapObject item) { rgnFile.addMapObject(item); } Index: src/uk/me/parabola/mkgmap/build/Locator.java =================================================================== --- src/uk/me/parabola/mkgmap/build/Locator.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/Locator.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * The Locator tries to fill missing country, region, postal coude information + * + * The algorithm works like this: + * + * 1. Step: Go through all cities an check if they have useful country region info + * The best case is if the tags is_in:country and is_in:county are present thats easy. + * Some cities have is_in information that can be used. We check for three different + * formats: + * + * County, State, Country, Continent + * County, State, Country + * Continent, Country, State, County, ... + * + * In "openGeoDb countries" this info is pretty reliable since it was imported from a + * external db into osm. Other countries have very sparse is_in info. + * + * All this cities that have such info will end up in "city" list. All which lack + * such information in "location" list. + * + * 2. Step: Go through the "location" list and check if the is_in info has some relations + * to the cities we have info about. + * + * Especially hamlets often have no full is_in information. They only have one entry in + * is_in that points to the city they belong to. I will check if I can find the name + * of this city in the "City" list. If there are more with the same name I use the + * closest one. If we can't find the exact name I use fuzzy name search. Thats a + * workaround for german umlaute since sometimes there are used in the tags and + * sometimes there are written as ue ae oe. + * + * 3. Step: Do the same like in step 2 once again. This is used to support at least + * one level of recursion in is_in relations. + * + * If there is still no info found I use brute force and use the information from the + * next city. Has to be used for countries with poor is_in tagging. + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.build; + +import java.util.Collection; +import uk.me.parabola.mkgmap.general.MapPoint; +import uk.me.parabola.mkgmap.general.MapPointFastFindMap; +import uk.me.parabola.mkgmap.general.MapPointMultiMap; + +import java.util.Vector; + +public class Locator { + + private final MapPointFastFindMap cityMap = new MapPointFastFindMap(); + private final MapPointMultiMap fuzzyCityMap = new MapPointMultiMap(); + private final java.util.Vector<MapPoint> placesMap = new Vector<MapPoint>(); + + private LocatorConfig locConfig = new LocatorConfig(); + + static double totalTime = 0; + static long totalFinds = 0; + private int autoFillLevel = 0; + + public void addLocation(MapPoint p) + { + resolveIsInInfo(p); // Preprocess the is_in field + + if(autoFillLevel < 1 && p.getCity() == null) + { + // Without autofill city name is the name of the tag + p.setCity(p.getName()); + } + + if(p.getCity() != null) + { + cityMap.put(p.getCity(), p); + + fuzzyCityMap.put(fuzzyDecode(p.getCity()),p); + + if(p.getName() != null && p.getCity().equals(p.getName()) == false) // Name variants ? + fuzzyCityMap.put(fuzzyDecode(p.getName()),p); + } + else + { + // All other places which do not seam to be a real city has to resolved later + placesMap.add(p); + } + + } + + + + public void setAutoFillLevel(int level) + { + autoFillLevel = level; + } + + public void setDefaultCountry(String country, String abbr) + { + locConfig.setDefaultCountry(country, abbr); + } + + public String fixCountryString(String country) + { + return locConfig.fixCountryString(country); + } + + private String isCountry(String country) + { + return locConfig.isCountry(country); + } + + public String getCountryCode(String country) + { + return locConfig.getCountryCode(country); + } + + public int getPOIDispFlag(String country) + { + return locConfig.getPoiDispFlag(country); + } + + private boolean isOpenGeoDBCountry(String country) + { + // Countries that have open geo db data in osm + // Right now this are only germany, austria and swizerland + return locConfig.isOpenGeoDBCountry(country); + } + + private boolean isContinent(String continent) + { + return locConfig.isContinent(continent); + } + + + /** + * resolveIsInInfo tries to get country and region info out of the is_in field + * @param p Point to process + */ + private void resolveIsInInfo(MapPoint p) + { + if(p.getCountry() != null) + p.setCountry(fixCountryString(p.getCountry())); + + if(p.getCountry() != null && p.getRegion() != null && p.getCity() == null) + { + p.setCity(p.getName()); + return; + } + + if(p.getIsIn() != null) + { + String cityList[] = p.getIsIn().split(","); + + //System.out.println(p.getIsIn()); + + // is_in content is not well defined so we try our best to get some info out of it + // Format 1 popular in Germany: "County,State,Country,Continent" + + if(cityList.length > 1 && + isContinent(cityList[cityList.length-1])) // Is last a continent ? + { + // The one before contient should be the country + p.setCountry(fixCountryString(cityList[cityList.length-2].trim())); + + // aks the config which info to use for region info + int offset = locConfig.getRegionOffset(p.getCountry()) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[cityList.length-(offset+1)].trim()); + + } + + // Format 2 other way round: "Continent,Country,State,County" + + if(cityList.length > 1 && isContinent(cityList[0])) // Is first a continent ? + { + // The one before contient should be the country + p.setCountry(fixCountryString(cityList[1].trim())); + + int offset = locConfig.getRegionOffset(p.getCountry()) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[offset].trim()); + } + + // Format like this "County,State,Country" + + if(p.getCountry() == null && cityList.length > 0) + { + // I don't like to check for a list of countries but I don't want other stuff in country field + + String countryStr = isCountry(cityList[cityList.length-1]); + + if(countryStr != null) + { + p.setCountry(countryStr); + + int offset = locConfig.getRegionOffset(countryStr) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[cityList.length-(offset+1)].trim()); + } + } + } + + if(p.getCountry() != null && p.getRegion() != null && p.getCity() == null) + { + // In OpenGeoDB Countries I don't want to mess up the info which city is a real independent + // Community in all other countries I just have to do it + + if(isOpenGeoDBCountry(p.getCountry()) == false) + p.setCity(p.getName()); + } + } + + public MapPoint findNextPoint(MapPoint p) + { + long startTime = System.nanoTime(); + + MapPoint nextPoint = null; + + nextPoint = cityMap.findNextPoint(p); + + totalFinds++; + totalTime = totalTime + ((System.nanoTime() - startTime)/1e9); + return nextPoint; + } + + public MapPoint findByCityName(MapPoint p) + { + MapPoint near = null; + Double minDist = Double.MAX_VALUE; + Collection <MapPoint> nextCityList = null; + + if(p.getCity() == null) + return null; + + nextCityList = cityMap.getList(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; + else + return null; + } + + private MapPoint findCity(MapPoint place, boolean fuzzy) + { + MapPoint near = null; + Double minDist = Double.MAX_VALUE; + Collection <MapPoint> nextCityList = null; + + String isIn = place.getIsIn(); + + if(isIn != null) + { + String cityList[] = isIn.split(","); + + // Go through the isIn string and check if we find a city with this name + // Lets hope we find the next bigger city + + for(int i = 0; i < cityList.length; i++) + { + String biggerCityName=cityList[i].trim(); + + + if(fuzzy == false) + nextCityList = cityMap.getList(biggerCityName); + else + nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName)); + + if(nextCityList != null) + { + for (MapPoint nextCity: nextCityList) + { + Double dist = place.getLocation().distance(nextCity.getLocation()); + + if(dist < minDist) + { + minDist = dist; + near = nextCity; + } + } + } + } + + if (autoFillLevel > 3) // Some debug output to find suspicios relations + { + + if(near != null && minDist > 30000) + { + System.out.println("Locator: " + place.getName() + " is far away from " + + near.getName() + " " + (minDist/1000.0) + " km is_in" + place.getIsIn()); + if(nextCityList != null) + System.out.println("Number of cities with this name: " + nextCityList.size()); + } + + //if(near != null && fuzzy) + //{ + // System.out.println("Locator: " + place.getName() + " may belong to " + + // near.getName() + " is_in" + place.getIsIn()); + //} + } + } + + return near; + } + + public void resolve() { + + if(autoFillLevel < 0) + return; // Nothing to do if autofill is fulli disabled + + if(autoFillLevel > 2) + { + System.out.println("\nLocator City Map contains " + cityMap.size() + " entries"); + System.out.println("Locator Places Map contains " + placesMap.size() + " entries"); + } + + int runCount = 0; + int maxRuns = 2; + int unresCount; + + do + { + unresCount=0; + + for (int i = 0; i < placesMap.size(); i++) + { + MapPoint place = placesMap.get(i); + + if(place != null) + { + + // first lets try exact name + + MapPoint near = findCity(place, false); + + + // if this didn't worked try to workaround german umlaute + + if(near == null) + near = findCity(place, true); + + if(autoFillLevel > 3 && near == null && (runCount + 1) == maxRuns) + { + if(place.getIsIn() != null) + System.out.println("Locator: CAN't locate " + place.getName() + " is_in " + place.getIsIn() + + " " + place.getLocation().toOSMURL()); + } + + + if(near != null) + { + place.setCity(near.getCity()); + place.setZip(near.getZip()); + } + else if (autoFillLevel > 1 && (runCount + 1) == maxRuns) + { + // In the last resolve run just take info from the next known city + near = cityMap.findNextPoint(place); + } + + + if(near != null) + { + if(place.getRegion() == null) + place.setRegion(near.getRegion()); + + if(place.getCountry() == null) + place.setCountry(near.getCountry()); + + } + + if(near == null) + unresCount++; + + } + } + + for (int i = 0; i < placesMap.size(); i++) + { + MapPoint place = placesMap.get(i); + + if (place != null) + { + if( place.getCity() != null) + { + cityMap.put(place.getName(),place); + fuzzyCityMap.put(fuzzyDecode(place.getName()),place); + placesMap.set(i, null); + } + else if(autoFillLevel < 2 && (runCount + 1) == maxRuns) + { + place.setCity(place.getName()); + cityMap.put(place.getName(),place); + } + } + } + + runCount++; + + if(autoFillLevel > 2) + System.out.println("Locator City Map contains " + cityMap.size() + + " entries after resolver run " + runCount + " Still unresolved " + unresCount); + + } + while(unresCount > 0 && runCount < maxRuns); + + } + + public void printStat() + { + System.out.println("Locator Find called: " + totalFinds + " time"); + System.out.println("Locator Find time: " + totalTime + " s"); + + cityMap.printStat(); + } + + 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); + } + +} + Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java =================================================================== --- src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -21,12 +21,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap;
import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.imgfmt.app.lbl.City; import uk.me.parabola.imgfmt.app.lbl.Country; +import uk.me.parabola.imgfmt.app.lbl.Zip; +import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.lbl.LBLFile; import uk.me.parabola.imgfmt.app.lbl.POIRecord; import uk.me.parabola.imgfmt.app.lbl.Region; @@ -83,26 +83,55 @@ private static final int CLEAR_TOP_BITS = (32 - 15);
private final java.util.Map<MapPoint,POIRecord> poimap = new HashMap<MapPoint,POIRecord>(); - private final SortedMap<String, Object> sortedCities = new TreeMap<String, Object>(); + private final java.util.Map<MapPoint,City> cityMap = new HashMap<MapPoint,City>(); + private boolean doRoads;
+ private Locator locator = new Locator(); private Country country; private Region region;
- private String countryName = "UNITED KINGDOM"; - private String countryAbbr = "GBR"; + private String countryName = "COUNTRY"; + private String countryAbbr = "ABC"; private String regionName; private String regionAbbr; + private int locationAutofillLevel = 0; + private boolean poiAddresses = true; + private int poiDisplayFlags = 0;
public MapBuilder() { regionName = null; }
public void config(EnhancedProperties props) { + + String autoFillPar; + countryName = props.getProperty("country-name", countryName); countryAbbr = props.getProperty("country-abbr", countryAbbr); regionName = props.getProperty("region-name", null); regionAbbr = props.getProperty("region-abbr", null); + + if(props.getProperty("no-poi-address", null) != null) + poiAddresses = false; + + autoFillPar = props.getProperty("location-autofill", null); + + if(autoFillPar != null) + { + try + { + locationAutofillLevel = Integer.parseInt(autoFillPar); + } + catch (Exception e) + { + locationAutofillLevel = 1; + } + } + + locator.setAutoFillLevel(locationAutofillLevel); + + }
/** @@ -123,6 +152,7 @@ if(regionName != null) region = lblFile.createRegion(country, regionName, regionAbbr);
+ processCities(map, src); processPOIs(map, src); //preProcessRoads(map, src); processOverviews(map, src); @@ -157,52 +187,211 @@ }
/** - * First stage of handling POIs + * Processing of Cities * - * POIs need to be handled first, because we need the offsets - * in the LBL file. + * Fills the city list in lbl block that is required for find by name + * It also builds up information that is required to get address info + * for the POIs * * @param map The map. * @param src The map data. */ - private void processPOIs(Map map, MapDataSource src) { + private void processCities(Map map, MapDataSource src) { LBLFile lbl = map.getLblFile();
- // gpsmapedit doesn't sort the city names so to be - // friendly we generate the city objects in alphabetic - // order - to do that we first build a map from city - // name to the associated MapPoint - we don't want to - // be fooled by duplicate names so suffix the name - // with the object to make it unique + locator.setDefaultCountry(countryName, countryAbbr); + + // collect the names of the cities for (MapPoint p : src.getPoints()) { if(p.isCity() && p.getName() != null) - sortedCities.put(p.getName() + "@" + p, p); + locator.addLocation(p); // Put the city info the map for missing info }
- // now loop through the sorted keys and retrieve - // the MapPoint associated with the key - now we - // can create the City object and remember it for later - for (String s : sortedCities.keySet()) { - MapPoint p = (MapPoint)sortedCities.get(s); - City c; - if(region != null) - c = lbl.createCity(region, p.getName()); - else - c = lbl.createCity(country, p.getName()); - sortedCities.put(s, c); + if(locationAutofillLevel > 0) + locator.resolve(); // Try to fill missing information that include search of next city + + for (MapPoint p : src.getPoints()) + { + if(p.isCity() && p.getName() != null) + { + Country thisCountry; + Region thisRegion; + City thisCity; + + String CountryStr = p.getCountry(); + String RegionStr = p.getRegion(); + + if(CountryStr != null) + thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr)); + else + thisCountry = country; + + if(RegionStr != null) + { + thisRegion = lbl.createRegion(thisCountry,RegionStr, null); + } + else + thisRegion = region; + + if(thisRegion != null) + thisCity = lbl.createCity(thisRegion, p.getName(), true); + else + thisCity = lbl.createCity(thisCountry, p.getName(), true); + + cityMap.put(p, thisCity); + } } - // if point has a nearest city, create a POIRecord to - // reference it + } + + private void processPOIs(Map map, MapDataSource src) { + + LBLFile lbl = map.getLblFile(); + long poiAddrCountr = 0; + boolean checkedForPoiDispFlag = false; + boolean doAutofill; + for (MapPoint p : src.getPoints()) { - MapPoint nearestCityPoint = p.getNearestCityPoint(); - if(nearestCityPoint != null && p.getName() != null) { - POIRecord r = lbl.createPOI(p.getName()); - City nearestCity = (City)sortedCities.get(nearestCityPoint.getName() + "@" + nearestCityPoint); - r.setCityIndex(nearestCity.getIndex()); + + if(p.isCity() == false && + (p.isRoadNamePOI() || poiAddresses)) + { + if(locationAutofillLevel > 0 || p.isRoadNamePOI()) + doAutofill = true; + else + doAutofill = false; + + + String CountryStr = p.getCountry(); + String RegionStr = p.getRegion(); + String ZipStr = p.getZip(); + String CityStr = p.getCity(); + boolean guessed = false; + + if(CityStr != null || ZipStr != null ||RegionStr != null || CountryStr != null) + poiAddrCountr++; + + if(CountryStr != null) + CountryStr = locator.fixCountryString(CountryStr); + + if(CountryStr == null || RegionStr == null || (ZipStr == null && CityStr == null)) + { + MapPoint nextCity = locator.findByCityName(p); + + if(doAutofill && nextCity == null) + nextCity = locator.findNextPoint(p); + + if(nextCity != null) + { + guessed = true; + + if (CountryStr == null) CountryStr = nextCity.getCountry(); + if (RegionStr == null) RegionStr = nextCity.getRegion(); + + if(doAutofill) + { + if(ZipStr == null) + { + String CityZipStr = nextCity.getZip(); + + // Ignore list of Zips seperated by ; + + if(CityZipStr != null && CityZipStr.indexOf(',') < 0) + ZipStr = CityZipStr; + } + + if(CityStr == null) CityStr = nextCity.getCity(); + } + + } + } + + + if(CountryStr != null && checkedForPoiDispFlag == false) + { + // Different countries require different address notation + + poiDisplayFlags = locator.getPOIDispFlag(CountryStr); + checkedForPoiDispFlag = true; + } + + + if(p.isRoadNamePOI() && CityStr != null) + { + // If it is road POI add city name and street name into address info + p.setStreet(p.getName()); + p.setName(p.getName() + "/" + CityStr); + } + + POIRecord r = lbl.createPOI(p.getName()); + + if(CityStr != null) + { + Country thisCountry; + Region thisRegion; + City city; + + if(CountryStr != null) + thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr)); + else + thisCountry = country; + + if(RegionStr != null) + thisRegion = lbl.createRegion(thisCountry,RegionStr, null); + else + thisRegion = region; + + if(thisRegion != null) + city = lbl.createCity(thisRegion, CityStr, false); + else + city = lbl.createCity(thisCountry, CityStr, false); + + r.setCity(city); + + } + + if (ZipStr != null) + { + Zip zip = lbl.createZip(ZipStr); + r.setZipIndex(zip.getIndex()); + } + + if(p.getStreet() != null) + { + Label streetName = lbl.newLabel(p.getStreet()); + r.setStreetName(streetName); + } + else if (guessed == true && locationAutofillLevel > 0) + { + Label streetName = lbl.newLabel("FIX MY ADDRESS"); + r.setStreetName(streetName); + } + + if(p.getHouseNumber() != null) + { + if(r.setSimpleStreetNumber(p.getHouseNumber()) == false) + { + Label streetNumber = lbl.newLabel(p.getHouseNumber()); + r.setComplexStreetNumber(streetNumber); + } + } + + if(p.getPhone() != null) + { + if(r.setSimplePhoneNumber(p.getPhone()) == false) + { + Label phoneNumber = lbl.newLabel(p.getPhone()); + r.setComplexPhoneNumber(phoneNumber); + } + } + poimap.put(p, r); } } + + //System.out.println(poiAddrCountr + " POIs have address info"); + lbl.allPOIsDone(); + }
/** @@ -369,6 +558,9 @@ // The bounds of the map. map.setBounds(src.getBounds());
+ if(poiDisplayFlags != 0) // POI requested alterate address notation + map.setPoiDisplayFlags(poiDisplayFlags); + // You can add anything here. // But there has to be something, otherwise the map does not show up. // @@ -461,7 +653,8 @@ // retrieve the City created earlier for // this point and store the point info // in it - City c = (City)sortedCities.get(name + "@" + point); + City c = (City)cityMap.get(point); + if(pointIndex > 255) { System.err.println("Can't set city point index for " + name + " (too many indexed points in division)\n"); } else { Index: src/uk/me/parabola/mkgmap/build/LocatorConfig.java =================================================================== --- src/uk/me/parabola/mkgmap/build/LocatorConfig.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/LocatorConfig.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * The Locator tries to fill missing country, region, postal coude information + * + * To do so we analyse the is_in information and if this doesn't helps us we + * try to get info from the next known city + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ +package uk.me.parabola.mkgmap.build; + +import org.w3c.dom.*; +import javax.xml.parsers.*; +import java.io.*; + +import java.util.HashMap; + +public class LocatorConfig { + + private final java.util.Map<String,String> variantMap = new HashMap<String,String>(); + private final java.util.Map<String,String> abrMap = new HashMap<String,String>(); + private final java.util.Map<String,Boolean> geoDbMap = new HashMap<String,Boolean>(); + private final java.util.Map<String,Integer> regOffsetMap = new HashMap<String,Integer>(); + private final java.util.Map<String,Integer> poiDispFlagMap = new HashMap<String,Integer>(); + private final java.util.Map<String,Boolean> continentMap = new HashMap<String,Boolean>(); + + + public LocatorConfig() + { + loadConfig("/LocatorConfig.xml"); + } + + private void loadConfig(String fileName) + { + try + { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream inStream; + + try + { + inStream = new FileInputStream("resources/" + fileName); + } + catch (Exception ex) + { + inStream = null; + } + + if(inStream == null) // If not loaded from disk use from jar file + inStream = this.getClass().getResourceAsStream(fileName); + + Document document = builder.parse(inStream); + + Node rootNode = document.getDocumentElement(); + + if(rootNode.getNodeName().equals("locator")) + { + Node cNode = rootNode.getFirstChild(); + + while(cNode != null) + { + if(cNode.getNodeName().equals("continent")) + { + NamedNodeMap attr = cNode.getAttributes(); + Node nameTag = null; + + if(attr != null) + { + nameTag = attr.getNamedItem("name"); + if(nameTag != null) + addContinent(nameTag.getNodeValue()); + } + + } + + if(cNode.getNodeName().equals("country")) + { + NamedNodeMap attr = cNode.getAttributes(); + Node nameTag = null; + + if(attr != null) + { + nameTag = attr.getNamedItem("name"); + + Node abrTag = attr.getNamedItem("abr"); + + if(abrTag != null && nameTag != null) + addAbr(nameTag.getNodeValue(),abrTag.getNodeValue()); + + if(abrTag == null && nameTag != null) + addAbr(nameTag.getNodeValue(),""); + + Node geoTag = attr.getNamedItem("geodb"); + + if(nameTag != null && geoTag != null) + { + if(geoTag.getNodeValue().equals("1")) + addOpenGeoDb(nameTag.getNodeValue()); + } + + Node regionOffsetTag = attr.getNamedItem("regionOffset"); + + if(regionOffsetTag != null && nameTag != null) + { + addRegionOffset(nameTag.getNodeValue(),Integer.parseInt(regionOffsetTag.getNodeValue())); + } + + Node poiDispTag = attr.getNamedItem("poiDispFlag"); + + if(poiDispTag != null && nameTag != null) + { + addPoiDispTag(nameTag.getNodeValue(),Integer.decode(poiDispTag.getNodeValue())); + } + } + + Node cEntryNode = cNode.getFirstChild(); + while(cEntryNode != null) + { + if(cEntryNode.getNodeName().equals("variant")) + { + Node nodeText = cEntryNode.getFirstChild(); + + if(nodeText != null && nameTag != null) + addVariant(nameTag.getNodeValue(), nodeText.getNodeValue()); + + } + cEntryNode = cEntryNode.getNextSibling(); + } + } + + cNode = cNode.getNextSibling(); + } + } + else + { + System.out.println(fileName + "contains invalid root tag " + rootNode.getNodeName()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + //System.out.println("Something is wrong here"); + } + return; + } + + private void addVariant(String country, String variant) + { + String cStr = country.toUpperCase().trim(); + String vStr = variant.toUpperCase().trim(); + + //System.out.println(vStr + " -> " + cStr); + + variantMap.put(vStr,cStr); + } + + private void addAbr(String country, String abr) + { + String cStr = country.toUpperCase().trim(); + String aStr = abr.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + aStr); + + abrMap.put(cStr,aStr); + } + + private void addRegionOffset(String country, Integer offset) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + offset); + + regOffsetMap.put(cStr,offset); + } + + private void addPoiDispTag(String country, Integer flag) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + flag); + + poiDispFlagMap.put(cStr,flag); + } + + private void addOpenGeoDb(String country) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " openGeoDb"); + + geoDbMap.put(cStr,true); + + } + + private void addContinent(String continent) + { + String cStr = continent.toUpperCase().trim(); + + //System.out.println(cStr + " continent"); + + continentMap.put(cStr,true); + + } + + + public void setDefaultCountry(String country, String abbr) + { + addAbr(country, abbr); + } + + public String fixCountryString(String country) + { + String cStr = country.toUpperCase().trim(); + + String fixedString = variantMap.get(cStr); + + if(fixedString != null) + return fixedString; + else + return(cStr); + } + + public String isCountry(String country) + { + String cStr = fixCountryString(country); + + if(getCountryCode(cStr) != null) + return cStr; + else + return null; + + } + + public String getCountryCode(String country) + { + String cStr = country.toUpperCase().trim(); + return abrMap.get(cStr); + } + + public int getRegionOffset(String country) + { + String cStr = country.toUpperCase().trim(); + + Integer regOffset = regOffsetMap.get(cStr); + + if(regOffset != null) + return regOffset; + else + return 1; // Default is 1 the next string after before country + } + + public int getPoiDispFlag(String country) + { + String cStr = country.toUpperCase().trim(); + + Integer flag = poiDispFlagMap.get(cStr); + + if(flag != null) + return flag; + else + return 0; // Default is 1 the next string after before country + } + + public boolean isOpenGeoDBCountry(String country) + { + // Countries that have open geo db data in osm + // Right now this are only germany, austria and swizerland + + String cStr = country.toUpperCase().trim(); + + if(geoDbMap.get(cStr) != null) + return true; + + return false; + } + + public boolean isContinent(String continent) + { + String s = continent.toUpperCase().trim(); + + if(continentMap.get(s) != null) + return(true); + + return false; + } + + + + +} + Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java =================================================================== --- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -253,6 +253,11 @@ shape.setPoints(way.getPoints());
clipper.clipShape(shape, collector); + + GType pointType = nodeRules.resolveType(way); + + if(pointType != null) + shape.setPoiType(pointType.getType()); }
private void addPoint(Node node, GType gt) { @@ -271,6 +276,50 @@ ms.setType(gt.getType()); ms.setMinResolution(gt.getMinResolution()); ms.setMaxResolution(gt.getMaxResolution()); + + // Now try to get some address info for POIs + + String city = element.getTag("addr:city"); + String zip = element.getTag("addr:postcode"); + String street = element.getTag("addr:street"); + String houseNumber = element.getTag("addr:housenumber"); + String phone = element.getTag("phone"); + String isIn = element.getTag("is_in"); + String country = element.getTag("is_in:country"); + String region = element.getTag("is_in:county"); + + if(country != null) + country = element.getTag("addr:country"); + + if(zip == null) + zip = element.getTag("openGeoDB:postal_codes"); + + if(city == null) + city = element.getTag("openGeoDB:sort_name"); + + if(city != null) + ms.setCity(city); + + if(zip != null) + ms.setZip(zip); + + if(street != null) + ms.setStreet(street); + + if(houseNumber != null) + ms.setHouseNumber(houseNumber); + + if(isIn != null) + ms.setIsIn(isIn); + + if(phone != null) + ms.setPhone(phone); + + if(country != null) + ms.setCountry(country); + + if(region != null) + ms.setRegion(region); }
void addRoad(Way way, GType gt) { Index: src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -362,6 +362,23 @@ } catch (NumberFormatException e) { endLevel = 0; } + } else if (name.equals("ZipCode")) { + elem.setZip(recode(value)); + } else if (name.equals("CityName")) { + elem.setCity(recode(value)); + } else if (name.equals("StreetDesc")) { + elem.setStreet(recode(value)); + } else if (name.equals("HouseNumber")) { + elem.setHouseNumber(recode(value)); + } else if (name.equals("is_in")) { + elem.setIsIn(recode(value)); + } else if (name.equals("Phone")) { + elem.setPhone(recode(value)); + } else if (name.equals("CountryName")) { + elem.setCountry(recode(value)); + } else if (name.equals("RegionName")) { + //System.out.println("RegionName " + value); + elem.setRegion(recode(value)); } else { return false; } Index: src/uk/me/parabola/mkgmap/main/MapMaker.java =================================================================== --- src/uk/me/parabola/mkgmap/main/MapMaker.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/main/MapMaker.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -36,6 +36,8 @@ import uk.me.parabola.mkgmap.general.LoadableMapDataSource; import uk.me.parabola.mkgmap.general.MapLine; import uk.me.parabola.mkgmap.general.MapPoint; +import uk.me.parabola.mkgmap.general.MapShape; +import uk.me.parabola.mkgmap.general.MapPointFastFindMap; import uk.me.parabola.mkgmap.reader.plugin.MapReader;
/** @@ -49,6 +51,7 @@ public String makeMap(CommandArgs args, String filename) { try { LoadableMapDataSource src = loadFromFile(args, filename); + makeAreaPOIs(args, src); makeRoadNamePOIS(args, src); return makeMap(args, src); } catch (FormatException e) { @@ -135,6 +138,44 @@ return src; }
+ private void makeAreaPOIs(CommandArgs args, LoadableMapDataSource src) { + String s = args.getProperties().getProperty("add-pois-to-areas"); + if (s != null) { + + MapPointFastFindMap poiMap = new MapPointFastFindMap(); + + for (MapPoint point : src.getPoints()) + { + if(point.isRoadNamePOI() == false) // Don't put road pois in this list + poiMap.put(null, point); + } + + for (MapShape shape : src.getShapes()) { + String shapeName = shape.getName(); + + int pointType = shape.getPoiType(); + + // only make a point if the shape has a name and we know what type of point to make + if (pointType == 0) + continue; + + // check if there is not already a poi in that shape + + if(poiMap.findPointInShape(shape, pointType) == null) + { + MapPoint newPoint = new MapPoint(); + newPoint.setName(shapeName); + newPoint.setType(pointType); + newPoint.setLocation(shape.getLocation()); // TODO use centriod + src.getPoints().add(newPoint); + log.info("created POI ", shapeName, "from shape"); + } + } + } + + } + + void makeRoadNamePOIS(CommandArgs args, LoadableMapDataSource src) { String rnp = args.getProperties().getProperty("road-name-pois", null); // are road name POIS wanted? @@ -277,26 +318,10 @@ }
String name = road.getName(); - MapPoint nearestCity = null; - if(cities != null) { - double shortestDistance = 10000000; - for(MapPoint mp : cities) { - double distance = coord.distance(mp.getLocation()); - if(distance < shortestDistance) { - shortestDistance = distance; - nearestCity = mp; - } - } - } - MapPoint rnp = new MapPoint();
- if(nearestCity != null && nearestCity.getName() != null) { - //rnp.setNearestCityPoint(nearestCity); - name += "/" + nearestCity.getName(); - } - rnp.setName(name); + rnp.setRoadNamePOI(true); rnp.setType(type); rnp.setLocation(coord); return rnp; Index: src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * This is multimap to store city information for the Address Locator + * + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.general; + + +import java.util.Vector; +import java.util.HashMap; +import java.util.Collection; + +public class MapPointMultiMap{ + + private final java.util.Map<String,Vector<MapPoint>> map = new HashMap<String,Vector<MapPoint>>(); + + public MapPoint put(String name, MapPoint p) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list == null){ + + list = new Vector<MapPoint>(); + list.add(p); + map.put(name, list); + } + else + list.add(p); + + return p; + } + + public MapPoint get(String name) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list != null) + return list.elementAt(0); + else + return null; + } + + public Collection<MapPoint> getList(String name) + { + return map.get(name); + } +} \ No newline at end of file Index: src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * This is multimap to store city information for the Address Locator + * tt provides also a fast tile based nearest point search function + * + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.general; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.Vector; +import java.util.List; +import uk.me.parabola.imgfmt.app.Coord; + +public class MapPointFastFindMap{ + + private final java.util.Map<String,Vector<MapPoint>> map = new HashMap<String,Vector<MapPoint>>(); + private final java.util.Map<Long,Vector<MapPoint>> posMap = new HashMap<Long,Vector<MapPoint>>(); + private final java.util.Vector<MapPoint> points = new Vector<MapPoint>(); + + private static final long POS_HASH_DIV = 8000; // the smaller -> more tiles + private static final long POS_HASH_MUL = 10000; // multiplicator for latitude to create hash + + public MapPoint put(String name, MapPoint p) + { + Vector<MapPoint> list; + + if(name != null) + { + list = map.get(name); + + if(list == null){ + + list = new Vector<MapPoint>(); + list.add(p); + map.put(name, list); + } + else + list.add(p); + + points.add(p); + } + + long posHash = getPosHashVal(p.getLocation().getLatitude(), p.getLocation().getLongitude()); + + list = posMap.get(posHash); + + if(list == null) + { + list = new Vector<MapPoint>(); + list.add(p); + posMap.put(posHash, list); + } + else + list.add(p); + + return p; + } + + public MapPoint get(String name) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list != null) + return list.elementAt(0); + else + return null; + } + + public Collection<MapPoint> getList(String name) + { + return map.get(name); + } + + public long size() + { + return points.size(); + } + + public Collection<MapPoint> values() + { + return points; + } + + public MapPoint get(int index) + { + return points.get(index); + } + + public MapPoint set(int index, MapPoint p) + { + return points.set(index, p); + } + + public boolean remove(MapPoint p) + { + return points.remove(p); + } + + + public MapPoint findNextPoint(MapPoint p) + { + /* tile based search + + to prevent expensive linear search over all points we put the points + into tiles. We just search the tiles the point is in linear and the + sourounding tiles. If we don't find a point we have to search further + arround the central tile + + */ + + Vector<MapPoint> list; + double minDist = Double.MAX_VALUE; + MapPoint nextPoint = null; + + if(posMap.size() < 1) // No point in list + return nextPoint; + + long centLatitIdx = p.getLocation().getLatitude() / POS_HASH_DIV ; + long centLongiIdx = p.getLocation().getLongitude() / POS_HASH_DIV ; + long delta = 1; + + long latitIdx; + long longiIdx; + long posHash; + + do + { + // in the first step we only check our tile and the tiles sourinding us + + for(latitIdx = centLatitIdx - delta; latitIdx <= centLatitIdx + delta; latitIdx++) + for(longiIdx = centLongiIdx - delta; longiIdx <= centLongiIdx + delta; longiIdx++) + { + if(delta < 2 + || latitIdx == centLatitIdx - delta + || latitIdx == centLatitIdx + delta + || longiIdx == centLongiIdx - delta + || longiIdx == centLongiIdx + delta) + { + + posHash = latitIdx * POS_HASH_MUL + longiIdx; + + list = posMap.get(posHash); + + if(list != null) + { + + for (MapPoint actPoint: list) + { + double distance = actPoint.getLocation().distance(p.getLocation()); + + if(distance < minDist) + { + nextPoint = actPoint; + minDist = distance; + + } + } + } + } + } + delta ++; // We have to look in tiles farer away + } + while(nextPoint == null); + + return nextPoint; + } + + public MapPoint findPointInShape(MapShape shape, int pointType) + { + Vector<MapPoint> list; + List<Coord> points = shape.getPoints(); + MapPoint nextPoint = null; + long lastHashValue = -1; + long posHash; + + if(posMap.size() < 1) // No point in list + return nextPoint; + + for(int i=0; i < points.size(); i++) + { + posHash = getPosHashVal(points.get(i).getLatitude(),points.get(i).getLongitude()); + + if(posHash == lastHashValue) // Have we already checked this tile ? + continue; + + lastHashValue = posHash; + + list = posMap.get(posHash); + + if(list != null) + { + for (MapPoint actPoint: list) + { + if(pointType == 0 || actPoint.getType() == pointType) + { + if(shape.contains( actPoint.getLocation())) + return actPoint; + } + } + } + } + + return null; + } + + private long getPosHashVal(long lat, long lon) + { + long latitIdx = lat / POS_HASH_DIV ; + long longiIdx = lon / POS_HASH_DIV ; + + //System.out.println("LatIdx " + latitIdx + " LonIdx " + longiIdx); + + return latitIdx * POS_HASH_MUL + longiIdx; + } + + public void printStat() + { + System.out.println("Locator PosHashmap contains " + posMap.size() + " tiles"); + } +} \ No newline at end of file Index: src/uk/me/parabola/mkgmap/general/MapElement.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapElement.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapElement.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -15,6 +15,9 @@ */ package uk.me.parabola.mkgmap.general;
+import java.util.Map; +import java.util.HashMap; + import uk.me.parabola.imgfmt.app.Coord;
/** @@ -29,6 +32,13 @@
private int minResolution = 24; private int maxResolution = 24; + + private String zipCode; + private String city; + private String region; + private String country; + + private final Map<String, String> attributes = new HashMap<String, String>();
protected MapElement() { } @@ -53,9 +63,84 @@ }
public void setName(String name) { - this.name = name; + if(name != null) + this.name = name.toUpperCase(); }
+ public String getCity() { + return city; + } + + public void setCity(String city) { + if(city != null) + this.city = city.toUpperCase(); + } + + public String getZip() { + return zipCode; + } + + public void setZip(String zip) { + this.zipCode = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + if(country != null) + this.country = country.toUpperCase(); + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + if(region != null) + this.region = region.toUpperCase(); + } + + public String getStreet() { + return attributes.get("street"); + } + + public void setStreet(String street) { + attributes.put("street", street); + } + + public String getPhone() { + return attributes.get("phone"); + } + + public void setPhone(String phone) { + + if(phone.startsWith("00")) + { + phone = phone.replaceFirst("00","+"); + } + attributes.put("phone", phone); + } + + public String getHouseNumber() { + return attributes.get("houseNumber"); + } + + public void setHouseNumber(String houseNumber) { + attributes.put("houseNumber", houseNumber); + } + + public String getIsIn() { + return attributes.get("isIn"); + } + + public void setIsIn(String isIn) { + if(isIn != null) + attributes.put("isIn", isIn.toUpperCase()); + } + + /** * This is the type code that goes in the .img file so that the GPS device * knows what to display. Index: src/uk/me/parabola/mkgmap/general/MapPoint.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPoint.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapPoint.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -27,6 +27,7 @@ public class MapPoint extends MapElement { private Coord location; private MapPoint nearestCityPoint; + private boolean isRoadNamePoi = false;
public MapPoint() { } @@ -63,11 +64,11 @@ return type >= 0x0100 && type <= 0x1100; }
- public void setNearestCityPoint(MapPoint nearestCityPoint) { - this.nearestCityPoint = nearestCityPoint; + public void setRoadNamePOI(boolean isRoadNamePoi) { + this.isRoadNamePoi = isRoadNamePoi; }
- public MapPoint getNearestCityPoint() { - return nearestCityPoint; + public boolean isRoadNamePOI() { + return this.isRoadNamePoi; } } Index: src/uk/me/parabola/mkgmap/general/MapShape.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapShape.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapShape.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -15,6 +15,11 @@ */ package uk.me.parabola.mkgmap.general;
+import java.util.ArrayList; +import java.util.List; + +import uk.me.parabola.imgfmt.app.Coord; + /** * A shape or polygon is just the same as a line really as far as I can tell. * There are some things that you cannot do with them semantically. @@ -23,6 +28,8 @@ */ public class MapShape extends MapLine {// So top code can link objects from here
+ private int poiType = 0; + public MapShape() { }
@@ -38,5 +45,158 @@ throw new IllegalArgumentException( "can't set a direction on a polygon"); } + + public void setPoiType(int type) + { + this.poiType = type; + } + + public int getPoiType() + { + return this.poiType; + } + + /** + * Checks if a point is contained within this shape. Points on the + * edge of the shape are considered inside. + * + * @param co point to check + * @return true if point is in shape, false otherwise + */ + public boolean contains(Coord co) { + return contains(this.getPoints(), co, true); + } + + /* + * Checks if a point is contained within a shape. + * + * @param points points that define the shape + * @param target point to check + * @param onLineIsInside if a point on the line should be considered inside the shape + * @return true if point is contained within the shape, false if the target point is outside the shape + */ + private static boolean contains(List<Coord> points, Coord target, boolean onLineIsInside) { + // implementation of the Ray casting algorithm as described here: + // http://en.wikipedia.org/wiki/Point_in_polygon + // with inspiration from: + // http://www.visibone.com/inpoly/ + boolean inside = false; + if (points.size() < 3) + return false;
+ // complete the shape if we're dealing with a MapShape that is not closed + Coord start = points.get(0); + Coord end = points.get(points.size() - 1); + if (!start.equals(end)) { + // make copy of the shape's geometry + List<Coord> pointsTemp = new ArrayList<Coord>(points.size() + 1); + for (Coord coord : points) { + pointsTemp.add(new Coord(coord.getLatitude(), coord.getLongitude())); + } + pointsTemp.add(new Coord(start.getLatitude(), start.getLongitude())); + points = pointsTemp; + } + + int xtarget = target.getLatitude(); + int ytarget = target.getLongitude(); + + for (int i = 0; i < points.size() - 1; i++) { + + // apply transformation points to change target point to (0,0) + int x0 = points.get(i).getLatitude() - xtarget; + int y0 = points.get(i).getLongitude() - ytarget; + int x1 = points.get(i+1).getLatitude() - xtarget; + int y1 = points.get(i+1).getLongitude() - ytarget; + + // ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily + if (x0 > x1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + + // use (0,0) as target because points already transformed + if (isPointOnLine(x0, y0, x1, y1, 0, 0)) + return onLineIsInside; + + // explanation of if statement + // + // (x0 < 0 && x1 >= 0): + // are the x values between the y axis? only include points from the right + // with this check so that corners aren't counted twice + // + // (y0 * (x1 - x0) > (y1 - y0) * x0): + // from y = mx + b: + // => b = y0 ((y1 - y0) / (x1 - x0)) * x0 + // for intersection, b > 0 + // from y = mx + b, b = y - mx + // => y - mx > 0 + // => y0 - ((y1 - y0) / (x1 - x0)) * x0 > 0 + // => y0 > ((y1 - y0) / (x1 - x0)) * x0 + // from 'if (x0 > x1)', x1 >= x0 + // => x1 - x0 >=0 + // => y0 * (x1 - x0) > (y1 - y0) * x0 + if ((x0 < 0 && x1 >= 0) && (y0 * (x1 - x0)) > ((y1 - y0) * x0)) + inside = !inside; + } + + return inside; + } + + /* + * Checks if a point is on a line. + * + * @param x0 x value of first point in line + * @param y0 y value of first point in line + * @param x1 x value of second point in line + * @param y1 y value of second point in line + * @param xt x value of target point + * @param yt y value of target point + * @return return true if point is on the line, false if the point isn't on the line + */ + private static boolean isPointOnLine(int x0, int y0, int x1, int y1, int xt, int yt) { + // this implementation avoids using doubles + // apply transformation points to change target point to (0,0) + x0 = x0 - xt; + y0 = y0 - yt; + x1 = x1 - xt; + y1 = y1 - yt; + + // ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily + if (x0 > x1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + + // if a point is on the edge of shape (on a line), it's considered outside the shape + // special case if line is on y-axis + if (x0 == 0 && x1 == 0) { + // ensure that y0 is smaller than y1 so that we can just check if the line intersects the x axis + if (y0 > y1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + // test to see if we have a vertical line touches x-axis + if (y0 <= 0 && y1 >= 0) + return true; + // checks if point is on the line, see comments in contain() for derivation of similar + // formula - left as an exercise to the reader ;) + } else if ((x0 <= 0 && x1 >= 0) && (y0 * (x1 - x0)) == ((y1 - y0) * x0)) { + return true; + } + return false; + } + + } Index: build.xml =================================================================== --- build.xml (.../upstream/mkgmap) (revision 280) +++ build.xml (.../work_poiaddr_area/mkgmap) (revision 280) @@ -78,8 +78,9 @@ <target name="build" depends="compile" > <copy todir="${build.classes}"> <fileset dir="${resources}"> - <include name="*.csv"/> + <include name="*.csv"/> <include name="*.properties"/> + <include name="*.xml"/> <include name="**/*.trans"/> <include name="styles/**"/> <include name="help/**"/> @@ -151,6 +152,7 @@ manifest="${resources}/MANIFEST.MF"> <include name="**/*.class"/> <include name="*.csv"/> + <include name="*.xml"/> <include name="*.properties"/> <include name="**/*.trans"/> <include name="styles/**"/> Index: resources/styles/default/polygons =================================================================== --- resources/styles/default/polygons (.../upstream/mkgmap) (revision 280) +++ resources/styles/default/polygons (.../work_poiaddr_area/mkgmap) (revision 280) @@ -6,6 +6,8 @@ amenity=supermarket [0x08 resolution 21] amenity=university [0x0a resolution 18]
+building=yes [0x13 resolution 18] + landuse=allotments [0x4e resolution 20] landuse=cemetary [0x1a resolution 18] landuse=cemetery [0x1a resolution 18] Index: resources/LocatorConfig.xml =================================================================== --- resources/LocatorConfig.xml (.../upstream/mkgmap) (revision 0) +++ resources/LocatorConfig.xml (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<locator> + <country name="Deutschland" abr="DEU" geodb="1" regionOffset="3" poiDispFlag="0xc"> + <variant>Bundesrepublik Deutschland</variant> + <variant>Germany</variant> + <variant>DE</variant> + </country> + <country name="Österreich" abr="AUT" geodb="1" poiDispFlag="0xc"> + <variant>Austria</variant> + <variant>AT</variant> + </country> + <country name="Schweiz" abr="CHE" geodb="1" poiDispFlag="0xc"> + <variant>Switzerland</variant> + <variant>CH</variant> + </country> + <country name="United Kingdom" abr="GBR"> + <variant>UK</variant> + <variant>GB</variant> + </country> + <country name="Italia" abr="ITA" regionOffset="2"> + <variant>Italy</variant> + <variant>IT</variant> + </country> + <country name="France" abr="FRA"> + </country> + <continent name="Europe"> + </continent> + <continent name="Africa"> + </continent> + <continent name="Asia"> + </continent> + <continent name="North America"> + </continent> + <continent name="South America"> + </continent> + <continent name="Oceania"> + </continent> +</locator> Index: resources/help/en/options =================================================================== --- resources/help/en/options (.../upstream/mkgmap) (revision 280) +++ resources/help/en/options (.../work_poiaddr_area/mkgmap) (revision 280) @@ -120,6 +120,11 @@ Generate a POI for each named road. By default, the POIs' Garmin type code is 0x640a. If desired, a different type code can be specified with this option. + +--add-pois-to-areas + Generate a POI for each area. The POIs are created after the style + is applied and only for polygon types that have a reasonable point + equivalent.
--tdbfile Write a .tdb file. @@ -134,6 +139,24 @@ same area, you can see through this map and see the lower map too. Useful for contour line maps among other things.
+--no-poi-address + Disable address / phone information to POIs. Address info is read according to + the "Karlsruhe" tagging schema. Automatic filling of missing information could + be enabled using the "location-autofill" option. + +--location-autofill=''number'' + Controls how country region info is gathered for cities / streets and pois + + 0 (Default) The country region info is gathered by analysis of the cities is_in tags. + If no country region info is present the default passed default country region is used. + + 1 Additional analysis of partial is_in info to get relations between hamlets and cities + + 2 Brute force search for nearest city with info if all methods before failed. Warning + cities my end up in the wrong country/region. + + 3 Enables debug output about suspicious relations that might cause wrong country region info + --version Output program version.
_______________________________________________ mkgmap-dev mailing list mkgmap-dev@lists.mkgmap.org.uk http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Hi Berni, I've tested out the POI branch and things are working for me, thanks! A small patch is attached to fix an outdated comment. Cheers, Ben Ben Konrath wrote:
Hi Berni,
Thanks for picking up my patch and for the improvements! :-) I noticed that it was just committed to the poi branch so I'll give it test and report back when I have some free time on Monday.
Cheers, Ben
On Sat, Feb 28, 2009 at 2:39 PM, Bernhard Heibler <bernhard@heibler.de> wrote:
Hi,
I have integrated Ben Konrath's Area POI patch into my code. I did the following changes to Ben's code:
- Replaced the hard coded area to poi type mapping with the build in rule sets. Shapes are checked against the point rules to add missing pois. - Replaced the linear search (to avoid duplicated pois) with tile based search - Add buildings to polygons style - Moved my helper classes to general directory
You have to add the --add-pois-to-areas to enable the Area POIs generation. I'm not sure if we should generate a new rule set in the style folder for this purpose. It works pretty good using the point rules but it might be confusing.
Thanks Ben for providing the ray cast code to check for points in polygons.
Thanks Berni.
Index: test/uk/me/parabola/mkgmap/general/PointInShapeTest.java =================================================================== --- test/uk/me/parabola/mkgmap/general/PointInShapeTest.java (.../upstream/mkgmap) (revision 0) +++ test/uk/me/parabola/mkgmap/general/PointInShapeTest.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,289 @@ +/** + * + */ +package uk.me.parabola.mkgmap.general; + + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import uk.me.parabola.imgfmt.app.Coord; + +/** + * @author ben + * + */ +public class PointInShapeTest { + + MapShape square, triangle, line; + int squareSize = 4; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Square + List<Coord> points = Arrays.asList( + new Coord(0, 0), + new Coord(0, squareSize), + new Coord(squareSize, squareSize), + new Coord(squareSize, 0), + new Coord(0,0) + ); + square = new MapShape(); + square.setPoints(points); + + // Triangle + points = Arrays.asList( + new Coord(0,0), + new Coord(4,4), + new Coord(8,0), + new Coord(0,0) + ); + triangle = new MapShape(); + triangle.setPoints(points); + + // Line + points = Arrays.asList( + new Coord(2,5), + new Coord(12,1) + ); + line = new MapShape(); + line.setPoints(points); + } + + @Test + public void testLinePointsInsideSquare() { + + // inside square, 1 unit from corners + List<Coord> points = Arrays.asList( + new Coord(1, squareSize/2), + new Coord(squareSize/2, squareSize - 1), + new Coord(squareSize - 1, squareSize/2), + new Coord(squareSize/2, 1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside square", + square.contains(coord)); + } + + // on the line + points = Arrays.asList( + new Coord(0, squareSize/2), + new Coord(squareSize/2, squareSize), + new Coord(squareSize, squareSize/2), + new Coord(squareSize/2, 0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square", + square.contains(coord)); + } + } + + @Test + public void testLinePointsOutsideSquare() { + + // outside square, 1 unit from line + List<Coord> points = Arrays.asList( + new Coord(-1, squareSize/2), + new Coord(squareSize/2, squareSize + 1), + new Coord(squareSize + 1, squareSize/2), + new Coord(squareSize/2, -1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square", + square.contains(coord)); + } + } + + @Test + public void testCornerPointsInsideSquare() { + // corner points + for (Coord cornerpoint : square.getPoints()) { + Coord co = new Coord(cornerpoint.getLatitude(), cornerpoint.getLongitude()); + assertTrue("corner point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside square", + square.contains(co)); + } + + // sub shape + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1; + int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be inside square", square.contains(co)); + } + + // tests above / below corner points, on the outside edge + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude(); + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // tests to the right / left side of corner points, on square edge + for (Coord cornerpoint : square.getPoints()) { + int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1; + int x = cornerpoint.getLatitude(); + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + } + + @Test + public void testCornerPointsOutsideSquare() { + + // tests above / below corner points, outside squate + for (Coord cornerpoint : square.getPoints()) { + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude(); + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // tests to the right / left side of corner points, outside square + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude(); + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + + // super shape + for (Coord cornerpoint : square.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co)); + } + } + + + @Test + public void testLinePointsInsideTriangle() { + // inside triangle, above / below lines + List<Coord> points = Arrays.asList( + new Coord(2,1), + new Coord(6,1), + new Coord(4,1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", + triangle.contains(coord)); + } + + // on lines + points = Arrays.asList( + new Coord(2,2), + new Coord(6,2), + new Coord(4,0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testLinePointsOutsideTriangle() { + // outside triangle, above / below lines + List<Coord> points = Arrays.asList( + new Coord(2,3), + new Coord(6,3), + new Coord(4,-1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testCornerPointsInsideTriangle() { + // corner points + for (Coord cornerpoint : triangle.getPoints()) { + assertTrue("point (" + cornerpoint.getLatitude() + ", " + cornerpoint.getLongitude() + ") should be outside triangle", + triangle.contains(cornerpoint)); + } + + // sub shape + List<Coord> points = Arrays.asList( + new Coord(2,1), + new Coord(4,3), + new Coord(6,1) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", + triangle.contains(coord)); + } + + // beside points, on edge + points = Arrays.asList( + new Coord(1,0), + new Coord(7,0) + ); + for (Coord coord : points) { + assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + } + + @Test + public void testCornerPointsOutsideTriangle() { + // above points + for (Coord coord : triangle.getPoints()) { + Coord co = new Coord(coord.getLatitude(), coord.getLongitude() + 1); + assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside triangle", + triangle.contains(co)); + } + + // outside triangle, beside / below lines + List<Coord> points = Arrays.asList( + new Coord(-1,0), + new Coord(0,-1), + new Coord(3,4), + new Coord(5,4), + new Coord(9,0), + new Coord(8,-1) + ); + for (Coord coord : points) { + assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle", + triangle.contains(coord)); + } + + // super shape + for (Coord cornerpoint : triangle.getPoints()) { + int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1; + int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1; + int x = cornerpoint.getLatitude() + xadd; + int y = cornerpoint.getLongitude() + yadd; + Coord co = new Coord(x, y); + assertFalse("point (" + x + ", " + y + ") should be outside triangle", triangle.contains(co)); + } + + } + + @Test + public void testLine() { + // midpoint + Coord co = new Coord(7,3); + assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside line", + line.contains(co)); + + } +} Index: src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -236,6 +236,10 @@ public void setMapId(int id) { mapId = id; } + + public void setPoiDisplayFlags(byte poiDisplayFlags) { + this.poiDisplayFlags = poiDisplayFlags; + }
public int getMapInfoSize() { return mapInfoSize; Index: src/uk/me/parabola/imgfmt/app/trergn/TREFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -315,6 +315,10 @@ header.setBounds(area); }
+ public void setPoiDisplayFlags(byte b) { + header.setPoiDisplayFlags(b); + } + public String[] getCopyrights() { if (!isReadable()) throw new IllegalStateException("not open for reading"); Index: src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -16,6 +16,7 @@ */ package uk.me.parabola.imgfmt.app.lbl;
+import java.util.Vector; import uk.me.parabola.imgfmt.app.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label;
@@ -23,6 +24,94 @@ * @author Steve Ratcliffe */ public class POIRecord { + + class SimpleStreetPhoneNumber // Helper Class to encode Street Phone Numbers + { + /** + Street and Phone numbers can be stored in two different ways in the poi record + Simple Number that only contain digits are coded in base 11 coding. + This helper class tries to code the given number. If the number contains other + chars like in 4a the coding fails and the caller has to use a Label instead + */ + + private byte encodedNumber[] = null; + private int encodedSize = 0; + + public boolean set(String number) + { + int i = 0; + int j = 0; + int val = 0; + + // remove sourounding whitespaces to increase chance for simple encoding + + number = number.trim(); + + encodedNumber = new byte[(number.length()/2)+2]; + + while(i < number.length()) + { + int c1 = 0; + int c2 = 0; + + c1 = decodeChar(number.charAt(i)); + i++; + + if(i < number.length()) + { + c2 = decodeChar(number.charAt(i)); + i++; + } + else + c2 = 10; + + if(c1 < 0 || c1 > 10 || c2 < 0 || c2 > 10) // Only 0-9 and - allowed + { + return false; + } + + val = c1 * 11 + c2; // Encode as base 11 + + if(i < 3 || i >= number.length()) // first byte needs special marking with 0x80 + val = val | 0x80; // If this is not set would be treated as label pointer + + encodedNumber[j++] = (byte)val; + } + + if((val & 0x80) == 0 || i < 3) // terminate string with 0x80 if not done + { + val = 0xF8; + encodedNumber[j++] = (byte)val; + } + + encodedSize = j; + + return true; + } + + public void write(ImgFileWriter writer) + { + for(int i = 0; i < encodedSize; i++) + writer.put(encodedNumber[i]); + } + + public boolean isUsed() + { + return (encodedSize > 0); + } + + public int getSize() + { + return encodedSize; + } + + private int decodeChar(char ch) + { + return (ch - '0'); + } + + } + public static final byte HAS_STREET_NUM = 0x01; public static final byte HAS_STREET = 0x02; public static final byte HAS_CITY = 0x04; @@ -43,13 +132,16 @@ private int offset = -1; private Label poiName;
- private int streetNumber; + private SimpleStreetPhoneNumber simpleStreetNumber = new SimpleStreetPhoneNumber(); + private SimpleStreetPhoneNumber simplePhoneNumber = new SimpleStreetPhoneNumber(); + private Label streetName; private Label streetNumberName; // Used for numbers such as 221b + private Label complexPhoneNumber; // Used for numbers such as 221b + + private City city = null; + private char zipIndex = 0;
- private char cityIndex ; - private char zipIndex ; - private String phoneNumber;
public void setLabel(Label label) { @@ -60,14 +152,35 @@ this.streetName = label; }
+ public boolean setSimpleStreetNumber(String streetNumber) + { + return simpleStreetNumber.set(streetNumber); + } + + public void setComplexStreetNumber(Label label) + { + streetNumberName = label; + } + + public boolean setSimplePhoneNumber(String phone) + { + return simplePhoneNumber.set(phone); + } + + public void setComplexPhoneNumber(Label label) + { + complexPhoneNumber = label; + } + + public void setZipIndex(int zipIndex) { this.zipIndex = (char) zipIndex; }
- public void setCityIndex(int cityIndex) + public void setCity(City city) { - this.cityIndex = (char) cityIndex; + this.city = city; }
void write(ImgFileWriter writer, byte POIGlobalFlags, int realofs, @@ -82,11 +195,21 @@ if (POIGlobalFlags != getPOIFlags()) writer.put(getWrittenPOIFlags(POIGlobalFlags));
+ if (streetNumberName != null) + { + int labOff = streetNumberName.getOffset(); + writer.put((byte)((labOff & 0x7F0000) >> 16)); + writer.putChar((char)(labOff & 0xFFFF)); + } + else if (simpleStreetNumber.isUsed()) + simpleStreetNumber.write(writer); + if (streetName != null) writer.put3(streetName.getOffset());
- if (cityIndex > 0) + if (city != null) { + char cityIndex = (char) city.getIndex(); if(numCities > 255) writer.putChar(cityIndex); else @@ -100,44 +223,60 @@ else writer.put((byte)zipIndex); } + + if (complexPhoneNumber != null) + { + int labOff = complexPhoneNumber.getOffset(); + writer.put((byte)((labOff & 0x7F0000) >> 16)); + writer.putChar((char)(labOff & 0xFFFF)); + } + else if (simplePhoneNumber.isUsed()) + simplePhoneNumber.write(writer); }
byte getPOIFlags() { byte b = 0; if (streetName != null) b |= HAS_STREET; - if (cityIndex > 0) + if (simpleStreetNumber.isUsed() || streetNumberName != null) + b |= HAS_STREET_NUM; + if (city != null) b |= HAS_CITY; if (zipIndex > 0) - b |= HAS_ZIP; + b |= HAS_ZIP; + if (simplePhoneNumber.isUsed() || complexPhoneNumber != null) + b |= HAS_PHONE; return b; }
byte getWrittenPOIFlags(byte POIGlobalFlags) { - int mask; - int flag = 0; - int j = 0; + int mask; + int flag = 0; + int j = 0;
- int usedFields = getPOIFlags(); + int usedFields = getPOIFlags();
- /* the local POI flag is really tricky - if a bit is not set in the global mask - we have to skip this bit in the local mask. - In other words the meaning of the local bits - change influenced by the global bits */ + /* the local POI flag is really tricky if a bit is not set in the global mask + we have to skip this bit in the local mask. In other words the meaning of the local bits + change influenced by the global bits */ + + for(byte i = 0; i < 6; i++) + { + mask = 1 << i;
- for (byte i = 0; i < 6; i++) { - mask = 1 << i; - - if ((mask & POIGlobalFlags) == mask) { - if ((mask & usedFields) == mask) - flag |= (1 << j); - j++; + if((mask & POIGlobalFlags) == mask) + { + if((mask & usedFields) == mask) + flag = flag | (1 << j); + j++; + } + }
- } - return (byte) flag; + flag = flag | 0x80; // gpsmapedit asserts for this bit set + + return (byte) flag; }
/** @@ -150,9 +289,17 @@ int size = 3; if (POIGlobalFlags != getPOIFlags()) size += 1; + if (simpleStreetNumber.isUsed()) + size += simpleStreetNumber.getSize(); + if (streetNumberName != null) + size += 3; + if (simplePhoneNumber.isUsed()) + size += simplePhoneNumber.getSize(); + if (complexPhoneNumber != null) + size += 3; if (streetName != null) - size += 3; - if (cityIndex > 0) + size += 3; + if (city != null) { /* depending on how many cities are in the LBL block we have @@ -160,9 +307,9 @@ */
if(numCities > 255) - size += 2; + size += 2; else - size += 1; + size += 1; } if (zipIndex > 0) { Index: src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -20,6 +20,8 @@ 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.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label; @@ -33,7 +35,8 @@ public class PlacesFile { private final Map<String, Country> countries = new LinkedHashMap<String, Country>(); private final Map<String, Region> regions = new LinkedHashMap<String, Region>(); - private final List<City> cities = new ArrayList<City>(); + private final Map<String, City> cities = new LinkedHashMap<String, City>(); + private final SortedMap<String, City> cityList = new TreeMap<String, City>(); private final Map<String, Zip> postalCodes = new LinkedHashMap<String, Zip>(); private final List<POIRecord> pois = new ArrayList<POIRecord>();
@@ -62,8 +65,12 @@ r.write(writer); placeHeader.endRegions(writer.position());
- for (City c : cities) + for (String s : cityList.keySet()) + { + City c = cityList.get(s); c.write(writer); + } + placeHeader.endCity(writer.position());
int poistart = writer.position(); @@ -79,56 +86,122 @@ }
Country createCountry(String name, String abbr) { - Country c = new Country(countries.size()+1); - + String s = abbr != null ? name + (char)0x1d + abbr : name; + + Country c = countries.get(s); + + if(c == null) + { + c = new Country(countries.size()+1);
- Label l = lblFile.newLabel(s); - c.setLabel(l); - - countries.put(name, c); + Label l = lblFile.newLabel(s); + c.setLabel(l); + countries.put(s, c); + } return c; }
Region createRegion(Country country, String name, String abbr) { - Region r = new Region(country, regions.size()+1); - + String s = abbr != null ? name + (char)0x1d + abbr : name;
- Label l = lblFile.newLabel(s); - r.setLabel(l); - - regions.put(name, r); + String uniqueRegionName = s.toUpperCase().concat(Long.toString(country.getIndex())); + + Region r = regions.get(uniqueRegionName); + + if(r == null) + { + r = new Region(country, regions.size()+1); + Label l = lblFile.newLabel(s); + r.setLabel(l); + regions.put(uniqueRegionName, r); + } return r; }
- City createCity(Country country, String name) { - City c = new City(country, cities.size()+1); + City createCity(Country country, String name, boolean unique) { + + String uniqueCityName = name.toUpperCase().concat("_C").concat(Long.toString(country.getIndex())); + + City c = null;
- Label l = lblFile.newLabel(name); - c.setLabel(l); // label may be ignored if pointref is set + if(!unique) + c = cities.get(uniqueCityName); + + if(c == null) + { + c = new City(country);
- cities.add(c); + Label l = lblFile.newLabel(name); + c.setLabel(l); + + /* + Adding 0 in between is important to get right sort order !!! + We have to make sure that "Kirchdorf" gets sorted before "Kirchdorf am Inn" + If this order is not correct nuvi would not find right city + */ + + cityList.put(name + " 0" + c, c); + cities.put(uniqueCityName, c); + } + return c; - } + }
- City createCity(Region region, String name) { - City c = new City(region, cities.size()+1); + City createCity(Region region, String name, boolean unique) { + + String uniqueCityName = name.toUpperCase().concat("_R").concat(Long.toString(region.getIndex())); + + City c = null;
- Label l = lblFile.newLabel(name); - c.setLabel(l); // label may be ignored if pointref is set + if(!unique) + c = cities.get(uniqueCityName); + + if(c == null) + { + c = new City(region);
- cities.add(c); + Label l = lblFile.newLabel(name); + c.setLabel(l); + + /* + Adding 0 in between is important to get right sort order !!! + We have to make sure that "Kirchdorf" gets sorted before "Kirchdorf am Inn" + If this order is not correct nuvi would not find right city + */ + + cityList.put(name + " 0" + c, c); + cities.put(uniqueCityName, c); + } + return c; }
+ private void sortCities() + { + int index = 1; + + for (String s : cityList.keySet()) + { + City c = cityList.get(s); + c.setIndex(index++); + } + } + Zip createZip(String code) { - Zip z = new Zip(postalCodes.size()+1); + + Zip z = postalCodes.get(code); + + if(z == null) + { + z = new Zip(postalCodes.size()+1);
- Label l = lblFile.newLabel(code); - z.setLabel(l); + Label l = lblFile.newLabel(code); + z.setLabel(l);
- postalCodes.put(code, z); + postalCodes.put(code, z); + } return z; }
@@ -146,6 +219,9 @@ }
void allPOIsDone() { + + sortCities(); + poisClosed = true;
byte poiFlags = 0; Index: src/uk/me/parabola/imgfmt/app/lbl/City.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/City.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/City.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -30,7 +30,7 @@ private static final int POINT_REF = 0x8000; private static final int REGION_IS_COUNTRY = 0x4000;
- private final int index; + private int index = -1;
private final Region region; private final Country country; @@ -49,13 +49,13 @@ // null if the location is being specified. private Label label;
- public City(Region region, int index) { + public City(Region region) { this.region = region; this.country = null; this.index = index; }
- public City(Country country, int index) { + public City(Country country) { this.country = country; this.region = null; this.index = index; @@ -83,9 +83,15 @@ }
public int getIndex() { + if (index == -1) + throw new IllegalStateException("Offset not known yet."); return index; }
+ public void setIndex(int index) { + this.index = index; + } + public void setLabel(Label label) { pointRef = false; this.label = label; Index: src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java =================================================================== --- src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -147,14 +147,18 @@ return places.createRegion(country, region, abbr); }
- public City createCity(Region region, String city) { - return places.createCity(region, city); + public City createCity(Region region, String city, boolean unique) { + return places.createCity(region, city, unique); }
- public City createCity(Country country, String city) { - return places.createCity(country, city); + public City createCity(Country country, String city, boolean unique) { + return places.createCity(country, city, unique); }
+ public Zip createZip(String code) { + return places.createZip(code); + } + public void allPOIsDone() { places.allPOIsDone(); } Index: src/uk/me/parabola/imgfmt/app/map/Map.java =================================================================== --- src/uk/me/parabola/imgfmt/app/map/Map.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/imgfmt/app/map/Map.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -225,6 +225,14 @@ treFile.addPolygonOverview(ov); }
+ /** + * Set the point of interest flags. + * @param flags The POI flags. + */ + public void setPoiDisplayFlags(int flags) { + treFile.setPoiDisplayFlags((byte) flags); + } + public void addMapObject(MapObject item) { rgnFile.addMapObject(item); } Index: src/uk/me/parabola/mkgmap/build/Locator.java =================================================================== --- src/uk/me/parabola/mkgmap/build/Locator.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/Locator.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * The Locator tries to fill missing country, region, postal coude information + * + * The algorithm works like this: + * + * 1. Step: Go through all cities an check if they have useful country region info + * The best case is if the tags is_in:country and is_in:county are present thats easy. + * Some cities have is_in information that can be used. We check for three different + * formats: + * + * County, State, Country, Continent + * County, State, Country + * Continent, Country, State, County, ... + * + * In "openGeoDb countries" this info is pretty reliable since it was imported from a + * external db into osm. Other countries have very sparse is_in info. + * + * All this cities that have such info will end up in "city" list. All which lack + * such information in "location" list. + * + * 2. Step: Go through the "location" list and check if the is_in info has some relations + * to the cities we have info about. + * + * Especially hamlets often have no full is_in information. They only have one entry in + * is_in that points to the city they belong to. I will check if I can find the name + * of this city in the "City" list. If there are more with the same name I use the + * closest one. If we can't find the exact name I use fuzzy name search. Thats a + * workaround for german umlaute since sometimes there are used in the tags and + * sometimes there are written as ue ae oe. + * + * 3. Step: Do the same like in step 2 once again. This is used to support at least + * one level of recursion in is_in relations. + * + * If there is still no info found I use brute force and use the information from the + * next city. Has to be used for countries with poor is_in tagging. + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.build; + +import java.util.Collection; +import uk.me.parabola.mkgmap.general.MapPoint; +import uk.me.parabola.mkgmap.general.MapPointFastFindMap; +import uk.me.parabola.mkgmap.general.MapPointMultiMap; + +import java.util.Vector; + +public class Locator { + + private final MapPointFastFindMap cityMap = new MapPointFastFindMap(); + private final MapPointMultiMap fuzzyCityMap = new MapPointMultiMap(); + private final java.util.Vector<MapPoint> placesMap = new Vector<MapPoint>(); + + private LocatorConfig locConfig = new LocatorConfig(); + + static double totalTime = 0; + static long totalFinds = 0; + private int autoFillLevel = 0; + + public void addLocation(MapPoint p) + { + resolveIsInInfo(p); // Preprocess the is_in field + + if(autoFillLevel < 1 && p.getCity() == null) + { + // Without autofill city name is the name of the tag + p.setCity(p.getName()); + } + + if(p.getCity() != null) + { + cityMap.put(p.getCity(), p); + + fuzzyCityMap.put(fuzzyDecode(p.getCity()),p); + + if(p.getName() != null && p.getCity().equals(p.getName()) == false) // Name variants ? + fuzzyCityMap.put(fuzzyDecode(p.getName()),p); + } + else + { + // All other places which do not seam to be a real city has to resolved later + placesMap.add(p); + } + + } + + + + public void setAutoFillLevel(int level) + { + autoFillLevel = level; + } + + public void setDefaultCountry(String country, String abbr) + { + locConfig.setDefaultCountry(country, abbr); + } + + public String fixCountryString(String country) + { + return locConfig.fixCountryString(country); + } + + private String isCountry(String country) + { + return locConfig.isCountry(country); + } + + public String getCountryCode(String country) + { + return locConfig.getCountryCode(country); + } + + public int getPOIDispFlag(String country) + { + return locConfig.getPoiDispFlag(country); + } + + private boolean isOpenGeoDBCountry(String country) + { + // Countries that have open geo db data in osm + // Right now this are only germany, austria and swizerland + return locConfig.isOpenGeoDBCountry(country); + } + + private boolean isContinent(String continent) + { + return locConfig.isContinent(continent); + } + + + /** + * resolveIsInInfo tries to get country and region info out of the is_in field + * @param p Point to process + */ + private void resolveIsInInfo(MapPoint p) + { + if(p.getCountry() != null) + p.setCountry(fixCountryString(p.getCountry())); + + if(p.getCountry() != null && p.getRegion() != null && p.getCity() == null) + { + p.setCity(p.getName()); + return; + } + + if(p.getIsIn() != null) + { + String cityList[] = p.getIsIn().split(","); + + //System.out.println(p.getIsIn()); + + // is_in content is not well defined so we try our best to get some info out of it + // Format 1 popular in Germany: "County,State,Country,Continent" + + if(cityList.length > 1 && + isContinent(cityList[cityList.length-1])) // Is last a continent ? + { + // The one before contient should be the country + p.setCountry(fixCountryString(cityList[cityList.length-2].trim())); + + // aks the config which info to use for region info + int offset = locConfig.getRegionOffset(p.getCountry()) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[cityList.length-(offset+1)].trim()); + + } + + // Format 2 other way round: "Continent,Country,State,County" + + if(cityList.length > 1 && isContinent(cityList[0])) // Is first a continent ? + { + // The one before contient should be the country + p.setCountry(fixCountryString(cityList[1].trim())); + + int offset = locConfig.getRegionOffset(p.getCountry()) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[offset].trim()); + } + + // Format like this "County,State,Country" + + if(p.getCountry() == null && cityList.length > 0) + { + // I don't like to check for a list of countries but I don't want other stuff in country field + + String countryStr = isCountry(cityList[cityList.length-1]); + + if(countryStr != null) + { + p.setCountry(countryStr); + + int offset = locConfig.getRegionOffset(countryStr) + 1; + + if(cityList.length > offset) + p.setRegion(cityList[cityList.length-(offset+1)].trim()); + } + } + } + + if(p.getCountry() != null && p.getRegion() != null && p.getCity() == null) + { + // In OpenGeoDB Countries I don't want to mess up the info which city is a real independent + // Community in all other countries I just have to do it + + if(isOpenGeoDBCountry(p.getCountry()) == false) + p.setCity(p.getName()); + } + } + + public MapPoint findNextPoint(MapPoint p) + { + long startTime = System.nanoTime(); + + MapPoint nextPoint = null; + + nextPoint = cityMap.findNextPoint(p); + + totalFinds++; + totalTime = totalTime + ((System.nanoTime() - startTime)/1e9); + return nextPoint; + } + + public MapPoint findByCityName(MapPoint p) + { + MapPoint near = null; + Double minDist = Double.MAX_VALUE; + Collection <MapPoint> nextCityList = null; + + if(p.getCity() == null) + return null; + + nextCityList = cityMap.getList(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; + else + return null; + } + + private MapPoint findCity(MapPoint place, boolean fuzzy) + { + MapPoint near = null; + Double minDist = Double.MAX_VALUE; + Collection <MapPoint> nextCityList = null; + + String isIn = place.getIsIn(); + + if(isIn != null) + { + String cityList[] = isIn.split(","); + + // Go through the isIn string and check if we find a city with this name + // Lets hope we find the next bigger city + + for(int i = 0; i < cityList.length; i++) + { + String biggerCityName=cityList[i].trim(); + + + if(fuzzy == false) + nextCityList = cityMap.getList(biggerCityName); + else + nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName)); + + if(nextCityList != null) + { + for (MapPoint nextCity: nextCityList) + { + Double dist = place.getLocation().distance(nextCity.getLocation()); + + if(dist < minDist) + { + minDist = dist; + near = nextCity; + } + } + } + } + + if (autoFillLevel > 3) // Some debug output to find suspicios relations + { + + if(near != null && minDist > 30000) + { + System.out.println("Locator: " + place.getName() + " is far away from " + + near.getName() + " " + (minDist/1000.0) + " km is_in" + place.getIsIn()); + if(nextCityList != null) + System.out.println("Number of cities with this name: " + nextCityList.size()); + } + + //if(near != null && fuzzy) + //{ + // System.out.println("Locator: " + place.getName() + " may belong to " + + // near.getName() + " is_in" + place.getIsIn()); + //} + } + } + + return near; + } + + public void resolve() { + + if(autoFillLevel < 0) + return; // Nothing to do if autofill is fulli disabled + + if(autoFillLevel > 2) + { + System.out.println("\nLocator City Map contains " + cityMap.size() + " entries"); + System.out.println("Locator Places Map contains " + placesMap.size() + " entries"); + } + + int runCount = 0; + int maxRuns = 2; + int unresCount; + + do + { + unresCount=0; + + for (int i = 0; i < placesMap.size(); i++) + { + MapPoint place = placesMap.get(i); + + if(place != null) + { + + // first lets try exact name + + MapPoint near = findCity(place, false); + + + // if this didn't worked try to workaround german umlaute + + if(near == null) + near = findCity(place, true); + + if(autoFillLevel > 3 && near == null && (runCount + 1) == maxRuns) + { + if(place.getIsIn() != null) + System.out.println("Locator: CAN't locate " + place.getName() + " is_in " + place.getIsIn() + + " " + place.getLocation().toOSMURL()); + } + + + if(near != null) + { + place.setCity(near.getCity()); + place.setZip(near.getZip()); + } + else if (autoFillLevel > 1 && (runCount + 1) == maxRuns) + { + // In the last resolve run just take info from the next known city + near = cityMap.findNextPoint(place); + } + + + if(near != null) + { + if(place.getRegion() == null) + place.setRegion(near.getRegion()); + + if(place.getCountry() == null) + place.setCountry(near.getCountry()); + + } + + if(near == null) + unresCount++; + + } + } + + for (int i = 0; i < placesMap.size(); i++) + { + MapPoint place = placesMap.get(i); + + if (place != null) + { + if( place.getCity() != null) + { + cityMap.put(place.getName(),place); + fuzzyCityMap.put(fuzzyDecode(place.getName()),place); + placesMap.set(i, null); + } + else if(autoFillLevel < 2 && (runCount + 1) == maxRuns) + { + place.setCity(place.getName()); + cityMap.put(place.getName(),place); + } + } + } + + runCount++; + + if(autoFillLevel > 2) + System.out.println("Locator City Map contains " + cityMap.size() + + " entries after resolver run " + runCount + " Still unresolved " + unresCount); + + } + while(unresCount > 0 && runCount < maxRuns); + + } + + public void printStat() + { + System.out.println("Locator Find called: " + totalFinds + " time"); + System.out.println("Locator Find time: " + totalTime + " s"); + + cityMap.printStat(); + } + + 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); + } + +} + Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java =================================================================== --- src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -21,12 +21,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap;
import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.imgfmt.app.lbl.City; import uk.me.parabola.imgfmt.app.lbl.Country; +import uk.me.parabola.imgfmt.app.lbl.Zip; +import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.lbl.LBLFile; import uk.me.parabola.imgfmt.app.lbl.POIRecord; import uk.me.parabola.imgfmt.app.lbl.Region; @@ -83,26 +83,55 @@ private static final int CLEAR_TOP_BITS = (32 - 15);
private final java.util.Map<MapPoint,POIRecord> poimap = new HashMap<MapPoint,POIRecord>(); - private final SortedMap<String, Object> sortedCities = new TreeMap<String, Object>(); + private final java.util.Map<MapPoint,City> cityMap = new HashMap<MapPoint,City>(); + private boolean doRoads;
+ private Locator locator = new Locator(); private Country country; private Region region;
- private String countryName = "UNITED KINGDOM"; - private String countryAbbr = "GBR"; + private String countryName = "COUNTRY"; + private String countryAbbr = "ABC"; private String regionName; private String regionAbbr; + private int locationAutofillLevel = 0; + private boolean poiAddresses = true; + private int poiDisplayFlags = 0;
public MapBuilder() { regionName = null; }
public void config(EnhancedProperties props) { + + String autoFillPar; + countryName = props.getProperty("country-name", countryName); countryAbbr = props.getProperty("country-abbr", countryAbbr); regionName = props.getProperty("region-name", null); regionAbbr = props.getProperty("region-abbr", null); + + if(props.getProperty("no-poi-address", null) != null) + poiAddresses = false; + + autoFillPar = props.getProperty("location-autofill", null); + + if(autoFillPar != null) + { + try + { + locationAutofillLevel = Integer.parseInt(autoFillPar); + } + catch (Exception e) + { + locationAutofillLevel = 1; + } + } + + locator.setAutoFillLevel(locationAutofillLevel); + + }
/** @@ -123,6 +152,7 @@ if(regionName != null) region = lblFile.createRegion(country, regionName, regionAbbr);
+ processCities(map, src); processPOIs(map, src); //preProcessRoads(map, src); processOverviews(map, src); @@ -157,52 +187,211 @@ }
/** - * First stage of handling POIs + * Processing of Cities * - * POIs need to be handled first, because we need the offsets - * in the LBL file. + * Fills the city list in lbl block that is required for find by name + * It also builds up information that is required to get address info + * for the POIs * * @param map The map. * @param src The map data. */ - private void processPOIs(Map map, MapDataSource src) { + private void processCities(Map map, MapDataSource src) { LBLFile lbl = map.getLblFile();
- // gpsmapedit doesn't sort the city names so to be - // friendly we generate the city objects in alphabetic - // order - to do that we first build a map from city - // name to the associated MapPoint - we don't want to - // be fooled by duplicate names so suffix the name - // with the object to make it unique + locator.setDefaultCountry(countryName, countryAbbr); + + // collect the names of the cities for (MapPoint p : src.getPoints()) { if(p.isCity() && p.getName() != null) - sortedCities.put(p.getName() + "@" + p, p); + locator.addLocation(p); // Put the city info the map for missing info }
- // now loop through the sorted keys and retrieve - // the MapPoint associated with the key - now we - // can create the City object and remember it for later - for (String s : sortedCities.keySet()) { - MapPoint p = (MapPoint)sortedCities.get(s); - City c; - if(region != null) - c = lbl.createCity(region, p.getName()); - else - c = lbl.createCity(country, p.getName()); - sortedCities.put(s, c); + if(locationAutofillLevel > 0) + locator.resolve(); // Try to fill missing information that include search of next city + + for (MapPoint p : src.getPoints()) + { + if(p.isCity() && p.getName() != null) + { + Country thisCountry; + Region thisRegion; + City thisCity; + + String CountryStr = p.getCountry(); + String RegionStr = p.getRegion(); + + if(CountryStr != null) + thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr)); + else + thisCountry = country; + + if(RegionStr != null) + { + thisRegion = lbl.createRegion(thisCountry,RegionStr, null); + } + else + thisRegion = region; + + if(thisRegion != null) + thisCity = lbl.createCity(thisRegion, p.getName(), true); + else + thisCity = lbl.createCity(thisCountry, p.getName(), true); + + cityMap.put(p, thisCity); + } } - // if point has a nearest city, create a POIRecord to - // reference it + } + + private void processPOIs(Map map, MapDataSource src) { + + LBLFile lbl = map.getLblFile(); + long poiAddrCountr = 0; + boolean checkedForPoiDispFlag = false; + boolean doAutofill; + for (MapPoint p : src.getPoints()) { - MapPoint nearestCityPoint = p.getNearestCityPoint(); - if(nearestCityPoint != null && p.getName() != null) { - POIRecord r = lbl.createPOI(p.getName()); - City nearestCity = (City)sortedCities.get(nearestCityPoint.getName() + "@" + nearestCityPoint); - r.setCityIndex(nearestCity.getIndex()); + + if(p.isCity() == false && + (p.isRoadNamePOI() || poiAddresses)) + { + if(locationAutofillLevel > 0 || p.isRoadNamePOI()) + doAutofill = true; + else + doAutofill = false; + + + String CountryStr = p.getCountry(); + String RegionStr = p.getRegion(); + String ZipStr = p.getZip(); + String CityStr = p.getCity(); + boolean guessed = false; + + if(CityStr != null || ZipStr != null ||RegionStr != null || CountryStr != null) + poiAddrCountr++; + + if(CountryStr != null) + CountryStr = locator.fixCountryString(CountryStr); + + if(CountryStr == null || RegionStr == null || (ZipStr == null && CityStr == null)) + { + MapPoint nextCity = locator.findByCityName(p); + + if(doAutofill && nextCity == null) + nextCity = locator.findNextPoint(p); + + if(nextCity != null) + { + guessed = true; + + if (CountryStr == null) CountryStr = nextCity.getCountry(); + if (RegionStr == null) RegionStr = nextCity.getRegion(); + + if(doAutofill) + { + if(ZipStr == null) + { + String CityZipStr = nextCity.getZip(); + + // Ignore list of Zips seperated by ; + + if(CityZipStr != null && CityZipStr.indexOf(',') < 0) + ZipStr = CityZipStr; + } + + if(CityStr == null) CityStr = nextCity.getCity(); + } + + } + } + + + if(CountryStr != null && checkedForPoiDispFlag == false) + { + // Different countries require different address notation + + poiDisplayFlags = locator.getPOIDispFlag(CountryStr); + checkedForPoiDispFlag = true; + } + + + if(p.isRoadNamePOI() && CityStr != null) + { + // If it is road POI add city name and street name into address info + p.setStreet(p.getName()); + p.setName(p.getName() + "/" + CityStr); + } + + POIRecord r = lbl.createPOI(p.getName()); + + if(CityStr != null) + { + Country thisCountry; + Region thisRegion; + City city; + + if(CountryStr != null) + thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr)); + else + thisCountry = country; + + if(RegionStr != null) + thisRegion = lbl.createRegion(thisCountry,RegionStr, null); + else + thisRegion = region; + + if(thisRegion != null) + city = lbl.createCity(thisRegion, CityStr, false); + else + city = lbl.createCity(thisCountry, CityStr, false); + + r.setCity(city); + + } + + if (ZipStr != null) + { + Zip zip = lbl.createZip(ZipStr); + r.setZipIndex(zip.getIndex()); + } + + if(p.getStreet() != null) + { + Label streetName = lbl.newLabel(p.getStreet()); + r.setStreetName(streetName); + } + else if (guessed == true && locationAutofillLevel > 0) + { + Label streetName = lbl.newLabel("FIX MY ADDRESS"); + r.setStreetName(streetName); + } + + if(p.getHouseNumber() != null) + { + if(r.setSimpleStreetNumber(p.getHouseNumber()) == false) + { + Label streetNumber = lbl.newLabel(p.getHouseNumber()); + r.setComplexStreetNumber(streetNumber); + } + } + + if(p.getPhone() != null) + { + if(r.setSimplePhoneNumber(p.getPhone()) == false) + { + Label phoneNumber = lbl.newLabel(p.getPhone()); + r.setComplexPhoneNumber(phoneNumber); + } + } + poimap.put(p, r); } } + + //System.out.println(poiAddrCountr + " POIs have address info"); + lbl.allPOIsDone(); + }
/** @@ -369,6 +558,9 @@ // The bounds of the map. map.setBounds(src.getBounds());
+ if(poiDisplayFlags != 0) // POI requested alterate address notation + map.setPoiDisplayFlags(poiDisplayFlags); + // You can add anything here. // But there has to be something, otherwise the map does not show up. // @@ -461,7 +653,8 @@ // retrieve the City created earlier for // this point and store the point info // in it - City c = (City)sortedCities.get(name + "@" + point); + City c = (City)cityMap.get(point); + if(pointIndex > 255) { System.err.println("Can't set city point index for " + name + " (too many indexed points in division)\n"); } else { Index: src/uk/me/parabola/mkgmap/build/LocatorConfig.java =================================================================== --- src/uk/me/parabola/mkgmap/build/LocatorConfig.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/LocatorConfig.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * The Locator tries to fill missing country, region, postal coude information + * + * To do so we analyse the is_in information and if this doesn't helps us we + * try to get info from the next known city + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ +package uk.me.parabola.mkgmap.build; + +import org.w3c.dom.*; +import javax.xml.parsers.*; +import java.io.*; + +import java.util.HashMap; + +public class LocatorConfig { + + private final java.util.Map<String,String> variantMap = new HashMap<String,String>(); + private final java.util.Map<String,String> abrMap = new HashMap<String,String>(); + private final java.util.Map<String,Boolean> geoDbMap = new HashMap<String,Boolean>(); + private final java.util.Map<String,Integer> regOffsetMap = new HashMap<String,Integer>(); + private final java.util.Map<String,Integer> poiDispFlagMap = new HashMap<String,Integer>(); + private final java.util.Map<String,Boolean> continentMap = new HashMap<String,Boolean>(); + + + public LocatorConfig() + { + loadConfig("/LocatorConfig.xml"); + } + + private void loadConfig(String fileName) + { + try + { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream inStream; + + try + { + inStream = new FileInputStream("resources/" + fileName); + } + catch (Exception ex) + { + inStream = null; + } + + if(inStream == null) // If not loaded from disk use from jar file + inStream = this.getClass().getResourceAsStream(fileName); + + Document document = builder.parse(inStream); + + Node rootNode = document.getDocumentElement(); + + if(rootNode.getNodeName().equals("locator")) + { + Node cNode = rootNode.getFirstChild(); + + while(cNode != null) + { + if(cNode.getNodeName().equals("continent")) + { + NamedNodeMap attr = cNode.getAttributes(); + Node nameTag = null; + + if(attr != null) + { + nameTag = attr.getNamedItem("name"); + if(nameTag != null) + addContinent(nameTag.getNodeValue()); + } + + } + + if(cNode.getNodeName().equals("country")) + { + NamedNodeMap attr = cNode.getAttributes(); + Node nameTag = null; + + if(attr != null) + { + nameTag = attr.getNamedItem("name"); + + Node abrTag = attr.getNamedItem("abr"); + + if(abrTag != null && nameTag != null) + addAbr(nameTag.getNodeValue(),abrTag.getNodeValue()); + + if(abrTag == null && nameTag != null) + addAbr(nameTag.getNodeValue(),""); + + Node geoTag = attr.getNamedItem("geodb"); + + if(nameTag != null && geoTag != null) + { + if(geoTag.getNodeValue().equals("1")) + addOpenGeoDb(nameTag.getNodeValue()); + } + + Node regionOffsetTag = attr.getNamedItem("regionOffset"); + + if(regionOffsetTag != null && nameTag != null) + { + addRegionOffset(nameTag.getNodeValue(),Integer.parseInt(regionOffsetTag.getNodeValue())); + } + + Node poiDispTag = attr.getNamedItem("poiDispFlag"); + + if(poiDispTag != null && nameTag != null) + { + addPoiDispTag(nameTag.getNodeValue(),Integer.decode(poiDispTag.getNodeValue())); + } + } + + Node cEntryNode = cNode.getFirstChild(); + while(cEntryNode != null) + { + if(cEntryNode.getNodeName().equals("variant")) + { + Node nodeText = cEntryNode.getFirstChild(); + + if(nodeText != null && nameTag != null) + addVariant(nameTag.getNodeValue(), nodeText.getNodeValue()); + + } + cEntryNode = cEntryNode.getNextSibling(); + } + } + + cNode = cNode.getNextSibling(); + } + } + else + { + System.out.println(fileName + "contains invalid root tag " + rootNode.getNodeName()); + } + } + catch (Exception ex) + { + ex.printStackTrace(); + //System.out.println("Something is wrong here"); + } + return; + } + + private void addVariant(String country, String variant) + { + String cStr = country.toUpperCase().trim(); + String vStr = variant.toUpperCase().trim(); + + //System.out.println(vStr + " -> " + cStr); + + variantMap.put(vStr,cStr); + } + + private void addAbr(String country, String abr) + { + String cStr = country.toUpperCase().trim(); + String aStr = abr.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + aStr); + + abrMap.put(cStr,aStr); + } + + private void addRegionOffset(String country, Integer offset) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + offset); + + regOffsetMap.put(cStr,offset); + } + + private void addPoiDispTag(String country, Integer flag) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " -> " + flag); + + poiDispFlagMap.put(cStr,flag); + } + + private void addOpenGeoDb(String country) + { + String cStr = country.toUpperCase().trim(); + + //System.out.println(cStr + " openGeoDb"); + + geoDbMap.put(cStr,true); + + } + + private void addContinent(String continent) + { + String cStr = continent.toUpperCase().trim(); + + //System.out.println(cStr + " continent"); + + continentMap.put(cStr,true); + + } + + + public void setDefaultCountry(String country, String abbr) + { + addAbr(country, abbr); + } + + public String fixCountryString(String country) + { + String cStr = country.toUpperCase().trim(); + + String fixedString = variantMap.get(cStr); + + if(fixedString != null) + return fixedString; + else + return(cStr); + } + + public String isCountry(String country) + { + String cStr = fixCountryString(country); + + if(getCountryCode(cStr) != null) + return cStr; + else + return null; + + } + + public String getCountryCode(String country) + { + String cStr = country.toUpperCase().trim(); + return abrMap.get(cStr); + } + + public int getRegionOffset(String country) + { + String cStr = country.toUpperCase().trim(); + + Integer regOffset = regOffsetMap.get(cStr); + + if(regOffset != null) + return regOffset; + else + return 1; // Default is 1 the next string after before country + } + + public int getPoiDispFlag(String country) + { + String cStr = country.toUpperCase().trim(); + + Integer flag = poiDispFlagMap.get(cStr); + + if(flag != null) + return flag; + else + return 0; // Default is 1 the next string after before country + } + + public boolean isOpenGeoDBCountry(String country) + { + // Countries that have open geo db data in osm + // Right now this are only germany, austria and swizerland + + String cStr = country.toUpperCase().trim(); + + if(geoDbMap.get(cStr) != null) + return true; + + return false; + } + + public boolean isContinent(String continent) + { + String s = continent.toUpperCase().trim(); + + if(continentMap.get(s) != null) + return(true); + + return false; + } + + + + +} + Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java =================================================================== --- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -253,6 +253,11 @@ shape.setPoints(way.getPoints());
clipper.clipShape(shape, collector); + + GType pointType = nodeRules.resolveType(way); + + if(pointType != null) + shape.setPoiType(pointType.getType()); }
private void addPoint(Node node, GType gt) { @@ -271,6 +276,50 @@ ms.setType(gt.getType()); ms.setMinResolution(gt.getMinResolution()); ms.setMaxResolution(gt.getMaxResolution()); + + // Now try to get some address info for POIs + + String city = element.getTag("addr:city"); + String zip = element.getTag("addr:postcode"); + String street = element.getTag("addr:street"); + String houseNumber = element.getTag("addr:housenumber"); + String phone = element.getTag("phone"); + String isIn = element.getTag("is_in"); + String country = element.getTag("is_in:country"); + String region = element.getTag("is_in:county"); + + if(country != null) + country = element.getTag("addr:country"); + + if(zip == null) + zip = element.getTag("openGeoDB:postal_codes"); + + if(city == null) + city = element.getTag("openGeoDB:sort_name"); + + if(city != null) + ms.setCity(city); + + if(zip != null) + ms.setZip(zip); + + if(street != null) + ms.setStreet(street); + + if(houseNumber != null) + ms.setHouseNumber(houseNumber); + + if(isIn != null) + ms.setIsIn(isIn); + + if(phone != null) + ms.setPhone(phone); + + if(country != null) + ms.setCountry(country); + + if(region != null) + ms.setRegion(region); }
void addRoad(Way way, GType gt) { Index: src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -362,6 +362,23 @@ } catch (NumberFormatException e) { endLevel = 0; } + } else if (name.equals("ZipCode")) { + elem.setZip(recode(value)); + } else if (name.equals("CityName")) { + elem.setCity(recode(value)); + } else if (name.equals("StreetDesc")) { + elem.setStreet(recode(value)); + } else if (name.equals("HouseNumber")) { + elem.setHouseNumber(recode(value)); + } else if (name.equals("is_in")) { + elem.setIsIn(recode(value)); + } else if (name.equals("Phone")) { + elem.setPhone(recode(value)); + } else if (name.equals("CountryName")) { + elem.setCountry(recode(value)); + } else if (name.equals("RegionName")) { + //System.out.println("RegionName " + value); + elem.setRegion(recode(value)); } else { return false; } Index: src/uk/me/parabola/mkgmap/main/MapMaker.java =================================================================== --- src/uk/me/parabola/mkgmap/main/MapMaker.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/main/MapMaker.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -36,6 +36,8 @@ import uk.me.parabola.mkgmap.general.LoadableMapDataSource; import uk.me.parabola.mkgmap.general.MapLine; import uk.me.parabola.mkgmap.general.MapPoint; +import uk.me.parabola.mkgmap.general.MapShape; +import uk.me.parabola.mkgmap.general.MapPointFastFindMap; import uk.me.parabola.mkgmap.reader.plugin.MapReader;
/** @@ -49,6 +51,7 @@ public String makeMap(CommandArgs args, String filename) { try { LoadableMapDataSource src = loadFromFile(args, filename); + makeAreaPOIs(args, src); makeRoadNamePOIS(args, src); return makeMap(args, src); } catch (FormatException e) { @@ -135,6 +138,44 @@ return src; }
+ private void makeAreaPOIs(CommandArgs args, LoadableMapDataSource src) { + String s = args.getProperties().getProperty("add-pois-to-areas"); + if (s != null) { + + MapPointFastFindMap poiMap = new MapPointFastFindMap(); + + for (MapPoint point : src.getPoints()) + { + if(point.isRoadNamePOI() == false) // Don't put road pois in this list + poiMap.put(null, point); + } + + for (MapShape shape : src.getShapes()) { + String shapeName = shape.getName(); + + int pointType = shape.getPoiType(); + + // only make a point if the shape has a name and we know what type of point to make + if (pointType == 0) + continue; + + // check if there is not already a poi in that shape + + if(poiMap.findPointInShape(shape, pointType) == null) + { + MapPoint newPoint = new MapPoint(); + newPoint.setName(shapeName); + newPoint.setType(pointType); + newPoint.setLocation(shape.getLocation()); // TODO use centriod + src.getPoints().add(newPoint); + log.info("created POI ", shapeName, "from shape"); + } + } + } + + } + + void makeRoadNamePOIS(CommandArgs args, LoadableMapDataSource src) { String rnp = args.getProperties().getProperty("road-name-pois", null); // are road name POIS wanted? @@ -277,26 +318,10 @@ }
String name = road.getName(); - MapPoint nearestCity = null; - if(cities != null) { - double shortestDistance = 10000000; - for(MapPoint mp : cities) { - double distance = coord.distance(mp.getLocation()); - if(distance < shortestDistance) { - shortestDistance = distance; - nearestCity = mp; - } - } - } - MapPoint rnp = new MapPoint();
- if(nearestCity != null && nearestCity.getName() != null) { - //rnp.setNearestCityPoint(nearestCity); - name += "/" + nearestCity.getName(); - } - rnp.setName(name); + rnp.setRoadNamePOI(true); rnp.setType(type); rnp.setLocation(coord); return rnp; Index: src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * This is multimap to store city information for the Address Locator + * + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.general; + + +import java.util.Vector; +import java.util.HashMap; +import java.util.Collection; + +public class MapPointMultiMap{ + + private final java.util.Map<String,Vector<MapPoint>> map = new HashMap<String,Vector<MapPoint>>(); + + public MapPoint put(String name, MapPoint p) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list == null){ + + list = new Vector<MapPoint>(); + list.add(p); + map.put(name, list); + } + else + list.add(p); + + return p; + } + + public MapPoint get(String name) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list != null) + return list.elementAt(0); + else + return null; + } + + public Collection<MapPoint> getList(String name) + { + return map.get(name); + } +} \ No newline at end of file Index: src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2009 Bernhard Heibler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * This is multimap to store city information for the Address Locator + * tt provides also a fast tile based nearest point search function + * + * + * Author: Bernhard Heibler + * Create date: 02-Jan-2009 + */ + +package uk.me.parabola.mkgmap.general; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.Vector; +import java.util.List; +import uk.me.parabola.imgfmt.app.Coord; + +public class MapPointFastFindMap{ + + private final java.util.Map<String,Vector<MapPoint>> map = new HashMap<String,Vector<MapPoint>>(); + private final java.util.Map<Long,Vector<MapPoint>> posMap = new HashMap<Long,Vector<MapPoint>>(); + private final java.util.Vector<MapPoint> points = new Vector<MapPoint>(); + + private static final long POS_HASH_DIV = 8000; // the smaller -> more tiles + private static final long POS_HASH_MUL = 10000; // multiplicator for latitude to create hash + + public MapPoint put(String name, MapPoint p) + { + Vector<MapPoint> list; + + if(name != null) + { + list = map.get(name); + + if(list == null){ + + list = new Vector<MapPoint>(); + list.add(p); + map.put(name, list); + } + else + list.add(p); + + points.add(p); + } + + long posHash = getPosHashVal(p.getLocation().getLatitude(), p.getLocation().getLongitude()); + + list = posMap.get(posHash); + + if(list == null) + { + list = new Vector<MapPoint>(); + list.add(p); + posMap.put(posHash, list); + } + else + list.add(p); + + return p; + } + + public MapPoint get(String name) + { + Vector<MapPoint> list; + + list = map.get(name); + + if(list != null) + return list.elementAt(0); + else + return null; + } + + public Collection<MapPoint> getList(String name) + { + return map.get(name); + } + + public long size() + { + return points.size(); + } + + public Collection<MapPoint> values() + { + return points; + } + + public MapPoint get(int index) + { + return points.get(index); + } + + public MapPoint set(int index, MapPoint p) + { + return points.set(index, p); + } + + public boolean remove(MapPoint p) + { + return points.remove(p); + } + + + public MapPoint findNextPoint(MapPoint p) + { + /* tile based search + + to prevent expensive linear search over all points we put the points + into tiles. We just search the tiles the point is in linear and the + sourounding tiles. If we don't find a point we have to search further + arround the central tile + + */ + + Vector<MapPoint> list; + double minDist = Double.MAX_VALUE; + MapPoint nextPoint = null; + + if(posMap.size() < 1) // No point in list + return nextPoint; + + long centLatitIdx = p.getLocation().getLatitude() / POS_HASH_DIV ; + long centLongiIdx = p.getLocation().getLongitude() / POS_HASH_DIV ; + long delta = 1; + + long latitIdx; + long longiIdx; + long posHash; + + do + { + // in the first step we only check our tile and the tiles sourinding us + + for(latitIdx = centLatitIdx - delta; latitIdx <= centLatitIdx + delta; latitIdx++) + for(longiIdx = centLongiIdx - delta; longiIdx <= centLongiIdx + delta; longiIdx++) + { + if(delta < 2 + || latitIdx == centLatitIdx - delta + || latitIdx == centLatitIdx + delta + || longiIdx == centLongiIdx - delta + || longiIdx == centLongiIdx + delta) + { + + posHash = latitIdx * POS_HASH_MUL + longiIdx; + + list = posMap.get(posHash); + + if(list != null) + { + + for (MapPoint actPoint: list) + { + double distance = actPoint.getLocation().distance(p.getLocation()); + + if(distance < minDist) + { + nextPoint = actPoint; + minDist = distance; + + } + } + } + } + } + delta ++; // We have to look in tiles farer away + } + while(nextPoint == null); + + return nextPoint; + } + + public MapPoint findPointInShape(MapShape shape, int pointType) + { + Vector<MapPoint> list; + List<Coord> points = shape.getPoints(); + MapPoint nextPoint = null; + long lastHashValue = -1; + long posHash; + + if(posMap.size() < 1) // No point in list + return nextPoint; + + for(int i=0; i < points.size(); i++) + { + posHash = getPosHashVal(points.get(i).getLatitude(),points.get(i).getLongitude()); + + if(posHash == lastHashValue) // Have we already checked this tile ? + continue; + + lastHashValue = posHash; + + list = posMap.get(posHash); + + if(list != null) + { + for (MapPoint actPoint: list) + { + if(pointType == 0 || actPoint.getType() == pointType) + { + if(shape.contains( actPoint.getLocation())) + return actPoint; + } + } + } + } + + return null; + } + + private long getPosHashVal(long lat, long lon) + { + long latitIdx = lat / POS_HASH_DIV ; + long longiIdx = lon / POS_HASH_DIV ; + + //System.out.println("LatIdx " + latitIdx + " LonIdx " + longiIdx); + + return latitIdx * POS_HASH_MUL + longiIdx; + } + + public void printStat() + { + System.out.println("Locator PosHashmap contains " + posMap.size() + " tiles"); + } +} \ No newline at end of file Index: src/uk/me/parabola/mkgmap/general/MapElement.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapElement.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapElement.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -15,6 +15,9 @@ */ package uk.me.parabola.mkgmap.general;
+import java.util.Map; +import java.util.HashMap; + import uk.me.parabola.imgfmt.app.Coord;
/** @@ -29,6 +32,13 @@
private int minResolution = 24; private int maxResolution = 24; + + private String zipCode; + private String city; + private String region; + private String country; + + private final Map<String, String> attributes = new HashMap<String, String>();
protected MapElement() { } @@ -53,9 +63,84 @@ }
public void setName(String name) { - this.name = name; + if(name != null) + this.name = name.toUpperCase(); }
+ public String getCity() { + return city; + } + + public void setCity(String city) { + if(city != null) + this.city = city.toUpperCase(); + } + + public String getZip() { + return zipCode; + } + + public void setZip(String zip) { + this.zipCode = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + if(country != null) + this.country = country.toUpperCase(); + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + if(region != null) + this.region = region.toUpperCase(); + } + + public String getStreet() { + return attributes.get("street"); + } + + public void setStreet(String street) { + attributes.put("street", street); + } + + public String getPhone() { + return attributes.get("phone"); + } + + public void setPhone(String phone) { + + if(phone.startsWith("00")) + { + phone = phone.replaceFirst("00","+"); + } + attributes.put("phone", phone); + } + + public String getHouseNumber() { + return attributes.get("houseNumber"); + } + + public void setHouseNumber(String houseNumber) { + attributes.put("houseNumber", houseNumber); + } + + public String getIsIn() { + return attributes.get("isIn"); + } + + public void setIsIn(String isIn) { + if(isIn != null) + attributes.put("isIn", isIn.toUpperCase()); + } + + /** * This is the type code that goes in the .img file so that the GPS device * knows what to display. Index: src/uk/me/parabola/mkgmap/general/MapPoint.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapPoint.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapPoint.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -27,6 +27,7 @@ public class MapPoint extends MapElement { private Coord location; private MapPoint nearestCityPoint; + private boolean isRoadNamePoi = false;
public MapPoint() { } @@ -63,11 +64,11 @@ return type >= 0x0100 && type <= 0x1100; }
- public void setNearestCityPoint(MapPoint nearestCityPoint) { - this.nearestCityPoint = nearestCityPoint; + public void setRoadNamePOI(boolean isRoadNamePoi) { + this.isRoadNamePoi = isRoadNamePoi; }
- public MapPoint getNearestCityPoint() { - return nearestCityPoint; + public boolean isRoadNamePOI() { + return this.isRoadNamePoi; } } Index: src/uk/me/parabola/mkgmap/general/MapShape.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapShape.java (.../upstream/mkgmap) (revision 280) +++ src/uk/me/parabola/mkgmap/general/MapShape.java (.../work_poiaddr_area/mkgmap) (revision 280) @@ -15,6 +15,11 @@ */ package uk.me.parabola.mkgmap.general;
+import java.util.ArrayList; +import java.util.List; + +import uk.me.parabola.imgfmt.app.Coord; + /** * A shape or polygon is just the same as a line really as far as I can tell. * There are some things that you cannot do with them semantically. @@ -23,6 +28,8 @@ */ public class MapShape extends MapLine {// So top code can link objects from here
+ private int poiType = 0; + public MapShape() { }
@@ -38,5 +45,158 @@ throw new IllegalArgumentException( "can't set a direction on a polygon"); } + + public void setPoiType(int type) + { + this.poiType = type; + } + + public int getPoiType() + { + return this.poiType; + } + + /** + * Checks if a point is contained within this shape. Points on the + * edge of the shape are considered inside. + * + * @param co point to check + * @return true if point is in shape, false otherwise + */ + public boolean contains(Coord co) { + return contains(this.getPoints(), co, true); + } + + /* + * Checks if a point is contained within a shape. + * + * @param points points that define the shape + * @param target point to check + * @param onLineIsInside if a point on the line should be considered inside the shape + * @return true if point is contained within the shape, false if the target point is outside the shape + */ + private static boolean contains(List<Coord> points, Coord target, boolean onLineIsInside) { + // implementation of the Ray casting algorithm as described here: + // http://en.wikipedia.org/wiki/Point_in_polygon + // with inspiration from: + // http://www.visibone.com/inpoly/ + boolean inside = false; + if (points.size() < 3) + return false;
+ // complete the shape if we're dealing with a MapShape that is not closed + Coord start = points.get(0); + Coord end = points.get(points.size() - 1); + if (!start.equals(end)) { + // make copy of the shape's geometry + List<Coord> pointsTemp = new ArrayList<Coord>(points.size() + 1); + for (Coord coord : points) { + pointsTemp.add(new Coord(coord.getLatitude(), coord.getLongitude())); + } + pointsTemp.add(new Coord(start.getLatitude(), start.getLongitude())); + points = pointsTemp; + } + + int xtarget = target.getLatitude(); + int ytarget = target.getLongitude(); + + for (int i = 0; i < points.size() - 1; i++) { + + // apply transformation points to change target point to (0,0) + int x0 = points.get(i).getLatitude() - xtarget; + int y0 = points.get(i).getLongitude() - ytarget; + int x1 = points.get(i+1).getLatitude() - xtarget; + int y1 = points.get(i+1).getLongitude() - ytarget; + + // ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily + if (x0 > x1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + + // use (0,0) as target because points already transformed + if (isPointOnLine(x0, y0, x1, y1, 0, 0)) + return onLineIsInside; + + // explanation of if statement + // + // (x0 < 0 && x1 >= 0): + // are the x values between the y axis? only include points from the right + // with this check so that corners aren't counted twice + // + // (y0 * (x1 - x0) > (y1 - y0) * x0): + // from y = mx + b: + // => b = y0 ((y1 - y0) / (x1 - x0)) * x0 + // for intersection, b > 0 + // from y = mx + b, b = y - mx + // => y - mx > 0 + // => y0 - ((y1 - y0) / (x1 - x0)) * x0 > 0 + // => y0 > ((y1 - y0) / (x1 - x0)) * x0 + // from 'if (x0 > x1)', x1 >= x0 + // => x1 - x0 >=0 + // => y0 * (x1 - x0) > (y1 - y0) * x0 + if ((x0 < 0 && x1 >= 0) && (y0 * (x1 - x0)) > ((y1 - y0) * x0)) + inside = !inside; + } + + return inside; + } + + /* + * Checks if a point is on a line. + * + * @param x0 x value of first point in line + * @param y0 y value of first point in line + * @param x1 x value of second point in line + * @param y1 y value of second point in line + * @param xt x value of target point + * @param yt y value of target point + * @return return true if point is on the line, false if the point isn't on the line + */ + private static boolean isPointOnLine(int x0, int y0, int x1, int y1, int xt, int yt) { + // this implementation avoids using doubles + // apply transformation points to change target point to (0,0) + x0 = x0 - xt; + y0 = y0 - yt; + x1 = x1 - xt; + y1 = y1 - yt; + + // ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily + if (x0 > x1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + + // if a point is on the edge of shape (on a line), it's considered outside the shape + // special case if line is on y-axis + if (x0 == 0 && x1 == 0) { + // ensure that y0 is smaller than y1 so that we can just check if the line intersects the x axis + if (y0 > y1) { + int xtemp = x0; + int ytemp = y0; + x0 = x1; + y0 = y1; + x1 = xtemp; + y1 = ytemp; + } + // test to see if we have a vertical line touches x-axis + if (y0 <= 0 && y1 >= 0) + return true; + // checks if point is on the line, see comments in contain() for derivation of similar + // formula - left as an exercise to the reader ;) + } else if ((x0 <= 0 && x1 >= 0) && (y0 * (x1 - x0)) == ((y1 - y0) * x0)) { + return true; + } + return false; + } + + } Index: build.xml =================================================================== --- build.xml (.../upstream/mkgmap) (revision 280) +++ build.xml (.../work_poiaddr_area/mkgmap) (revision 280) @@ -78,8 +78,9 @@ <target name="build" depends="compile" > <copy todir="${build.classes}"> <fileset dir="${resources}"> - <include name="*.csv"/> + <include name="*.csv"/> <include name="*.properties"/> + <include name="*.xml"/> <include name="**/*.trans"/> <include name="styles/**"/> <include name="help/**"/> @@ -151,6 +152,7 @@ manifest="${resources}/MANIFEST.MF"> <include name="**/*.class"/> <include name="*.csv"/> + <include name="*.xml"/> <include name="*.properties"/> <include name="**/*.trans"/> <include name="styles/**"/> Index: resources/styles/default/polygons =================================================================== --- resources/styles/default/polygons (.../upstream/mkgmap) (revision 280) +++ resources/styles/default/polygons (.../work_poiaddr_area/mkgmap) (revision 280) @@ -6,6 +6,8 @@ amenity=supermarket [0x08 resolution 21] amenity=university [0x0a resolution 18]
+building=yes [0x13 resolution 18] + landuse=allotments [0x4e resolution 20] landuse=cemetary [0x1a resolution 18] landuse=cemetery [0x1a resolution 18] Index: resources/LocatorConfig.xml =================================================================== --- resources/LocatorConfig.xml (.../upstream/mkgmap) (revision 0) +++ resources/LocatorConfig.xml (.../work_poiaddr_area/mkgmap) (revision 280) @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<locator> + <country name="Deutschland" abr="DEU" geodb="1" regionOffset="3" poiDispFlag="0xc"> + <variant>Bundesrepublik Deutschland</variant> + <variant>Germany</variant> + <variant>DE</variant> + </country> + <country name="Österreich" abr="AUT" geodb="1" poiDispFlag="0xc"> + <variant>Austria</variant> + <variant>AT</variant> + </country> + <country name="Schweiz" abr="CHE" geodb="1" poiDispFlag="0xc"> + <variant>Switzerland</variant> + <variant>CH</variant> + </country> + <country name="United Kingdom" abr="GBR"> + <variant>UK</variant> + <variant>GB</variant> + </country> + <country name="Italia" abr="ITA" regionOffset="2"> + <variant>Italy</variant> + <variant>IT</variant> + </country> + <country name="France" abr="FRA"> + </country> + <continent name="Europe"> + </continent> + <continent name="Africa"> + </continent> + <continent name="Asia"> + </continent> + <continent name="North America"> + </continent> + <continent name="South America"> + </continent> + <continent name="Oceania"> + </continent> +</locator> Index: resources/help/en/options =================================================================== --- resources/help/en/options (.../upstream/mkgmap) (revision 280) +++ resources/help/en/options (.../work_poiaddr_area/mkgmap) (revision 280) @@ -120,6 +120,11 @@ Generate a POI for each named road. By default, the POIs' Garmin type code is 0x640a. If desired, a different type code can be specified with this option. + +--add-pois-to-areas + Generate a POI for each area. The POIs are created after the style + is applied and only for polygon types that have a reasonable point + equivalent.
--tdbfile Write a .tdb file. @@ -134,6 +139,24 @@ same area, you can see through this map and see the lower map too. Useful for contour line maps among other things.
+--no-poi-address + Disable address / phone information to POIs. Address info is read according to + the "Karlsruhe" tagging schema. Automatic filling of missing information could + be enabled using the "location-autofill" option. + +--location-autofill=''number'' + Controls how country region info is gathered for cities / streets and pois + + 0 (Default) The country region info is gathered by analysis of the cities is_in tags. + If no country region info is present the default passed default country region is used. + + 1 Additional analysis of partial is_in info to get relations between hamlets and cities + + 2 Brute force search for nearest city with info if all methods before failed. Warning + cities my end up in the wrong country/region. + + 3 Enables debug output about suspicious relations that might cause wrong country region info + --version Output program version.
_______________________________________________ mkgmap-dev mailing list mkgmap-dev@lists.mkgmap.org.uk http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev
Index: /home/ben/workspace/mkgmap2/src/uk/me/parabola/mkgmap/main/MapMaker.java =================================================================== --- /home/ben/workspace/mkgmap2/src/uk/me/parabola/mkgmap/main/MapMaker.java (revision 967) +++ /home/ben/workspace/mkgmap2/src/uk/me/parabola/mkgmap/main/MapMaker.java (working copy) @@ -155,7 +155,7 @@ int pointType = shape.getPoiType(); - // only make a point if the shape has a name and we know what type of point to make + // only make a point if we know the type if (pointType == 0) continue;

Bernhard Heibler schrieb:
You have to add the --add-pois-to-areas to enable the Area POIs generation. I'm not sure if we should generate a new rule set in the style folder for this purpose. It works pretty good using the point rules but it might be confusing.
I would like an extension to the polygon rules like tag=value {create-point} so that you can control, for which areas you want a POI created. Gruss Torsten

On Sun, Mar 1, 2009 at 8:03 AM, Torsten Leistikow <de_muur@gmx.de> wrote:
I would like an extension to the polygon rules like
tag=value {create-point}
so that you can control, for which areas you want a POI created.
I agree with this. (Although I have not had a chance to test the latest patch to see how it works.) Such an extension, would allow me to catch cases such as the following: building=cathedral {create-point 0x2c0b} # Create "place_of_worship" POI for cathedrals. Of course, I don't want to be demanding. ;-) Cheers!

On Sun, Mar 1, 2009 at 10:58 PM, Clinton Gladstone <clinton.gladstone@googlemail.com> wrote:
I agree with this. (Although I have not had a chance to test the latest patch to see how it works.) Such an extension, would allow me to catch cases such as the following:
building=cathedral {create-point 0x2c0b} # Create "place_of_worship" POI for cathedrals.
I have to take this back: now that I have tested the patch, I see that it does exactly what I would like. The patch is great: thanks! I would only suggest that you perhaps change the wording in the help options file, so it is clear what the --add-pois-to-areas option does. Something like the following might help: --add-pois-to-areas Generate a POI for each area. The POIs inherit the tags of the area polygon, and are resolved according to the rules in the points style file. The original phrase "only for polygon types that have a reasonable point equivalent." confused me at first, as I thought that the POIs would be restricted to a predefined list. Cheers!

Hi Could you all try out the poi branch containing Bernhard's POI additions out please. It should be fully upto date with the main trunk. I've created a pre-built jar here: http://www.mkgmap.org.uk/download/mkgmap-poi.jar If there are no comments with problems I will push it onto the trunk tommorow. Cheers, ..Steve

Could you all try out the poi branch containing Bernhard's POI additions out please. It should be fully upto date with the main trunk.
I have tried it out with mapsource and mapedit but not with a real gps. The road name POIs appear to be as expected except when the location-autofill option has a value of 2 or more - in that case, the city name always seems to be wildly wrong. It's almost as if a random city chosen from the cities in the map has been assigned to the road name POIs in a given region. I tried the add-pois-to-areas option and I like how the areas are now findable. That's it so far. Cheers, Mark

Hi Mark, thanks for testing.
The road name POIs appear to be as expected except when the location-autofill option has a value of 2 or more - in that case, the city name always seems to be wildly wrong. It's almost as if a random city chosen from the cities in the map has been assigned to the road name POIs in a given region.
Yes I can reproduce your problem with my UK test case. I think I found a solution without messing up the results in countries with geodb tagging. The reason behind this problem is that I would like to use only real city names in poi addresses. I don't want to use suburbs or hamlet names for this purpose. In countries with poor is_in tagging this went wrong and all places which I don't have any info about where not used for the poi addresses. I think now it should work for both cases ... I think we should really discuss how OSM tagging could be improved to make generation of reliable address information easier. The current is_in tag is really hard to handle and. I have detected so many variations. I would prefer the separated tags version like is_in:country, is_in:region, is_in:city. Anyway the incremental patch for the poi branch is attached and also adds US support to the LocatorConfig.xml file. Any contributions to the LocatorConfig.xml file are welcome. To do so you have look at some of your is_in tags. A simple "grep is_in my.osm | sort -u" should do it to get a quick overview. <country name="Deutschland" abr="DEU" geodb="1" regionOffset="3" poiDispFlag="0xc"> <variant>Bundesrepublik Deutschland</variant> <variant>Germany</variant> <variant>DE</variant> </country> name -> specifies the country name you want to see on the garmin. abr -> the abbreviation for this country used on the garmin geodb -> 1 for Germany, Austria, Swizterland that has imported data from geodb. Default is 0 regionOffset -> Which administrative level down from the country name should be used as region info. Default is 1. poiDispFlag -> Address display format: Default is 0 / 0xc show street before street number / show zip before city variant -> This are the different spellings of this country in the is_in tags you would expect Thanks Berni. Index: src/uk/me/parabola/mkgmap/build/Locator.java =================================================================== --- src/uk/me/parabola/mkgmap/build/Locator.java (revision 956) +++ src/uk/me/parabola/mkgmap/build/Locator.java (working copy) @@ -399,6 +399,15 @@ { // In the last resolve run just take info from the next known city near = cityMap.findNextPoint(place); + + if(near != null && near.getCountry() != null) + { + // In OpenGeoDB Countries I don't want to mess up the info which city is a + // real independent Community in all other countries I just have to do it + + if(isOpenGeoDBCountry(near.getCountry()) == false) + place.setCity(place.getName()); + } } Index: resources/LocatorConfig.xml =================================================================== --- resources/LocatorConfig.xml (revision 956) +++ resources/LocatorConfig.xml (working copy) @@ -23,6 +23,9 @@ </country> <country name="France" abr="FRA"> </country> + <country name="United States" abr="USA"> + <variant>USA</variant> + </country> <continent name="Europe"> </continent> <continent name="Africa">

Hi Bernard
Anyway the incremental patch for the poi branch is attached and also
The patch to Locator.java would not apply so I patched it by hand. I don't know why that should be, my source was synced with the poi branch. But the good news is that it no longer uses silly city names in the road name POIs when location-autofill=2. Cheers, Mark
participants (6)
-
Ben Konrath
-
Bernhard Heibler
-
Clinton Gladstone
-
Mark Burton
-
Steve Ratcliffe
-
Torsten Leistikow