[mkgmap-dev] [PATCH] POI Address + Area POIs v7 R942
From Ben Konrath ben at bagu.org on Tue Mar 3 20:17:45 GMT 2009
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 at 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 at lists.mkgmap.org.uk >> http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev >> -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: mkgmap-poi-branch-comment-update.patch Url: http://lists.mkgmap.org.uk/pipermail/mkgmap-dev/attachments/20090303/f81cd53e/attachment.pl
- Previous message: [mkgmap-dev] [PATCH] POI Address + Area POIs v7 R942
- Next message: [mkgmap-dev] [PATCH] POI Address + Area POIs v7 R942
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
More information about the mkgmap-dev mailing list