
Hi, here is the updated POI address patch for the current trunk R942: - Poi address information is now added by default -- Option -no-poi-address added to switch the feature off - Check if zip tag contains a list of zips added - By default heuristic is disabled. Exception are: -- Name of nearest city is added to street POIs (if street POIs are enabled) -- If POI contains a city name tag. Country region info is taken from city with same name in the neighborhood - You can use the --location-autofill=X option to enable heuristic if you like Thanks Berni. Index: src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java =================================================================== --- src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../upstream/mkgmap) (revision 271) +++ src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/imgfmt/app/trergn/TREFile.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/imgfmt/app/lbl/City.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/imgfmt/app/map/Map.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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/MapPointMultiMap.java =================================================================== --- src/uk/me/parabola/mkgmap/build/MapPointMultiMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/MapPointMultiMap.java (.../work_poiaddr/mkgmap) (revision 271) @@ -0,0 +1,66 @@ +/* + * 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.build; + + +import java.util.Vector; +import java.util.HashMap; +import java.util.Collection; +import uk.me.parabola.mkgmap.general.MapPoint; + +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/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/mkgmap) (revision 271) @@ -0,0 +1,474 @@ +/* + * 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 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 271) +++ src/uk/me/parabola/mkgmap/build/MapBuilder.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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/MapPointFastFindMap.java =================================================================== --- src/uk/me/parabola/mkgmap/build/MapPointFastFindMap.java (.../upstream/mkgmap) (revision 0) +++ src/uk/me/parabola/mkgmap/build/MapPointFastFindMap.java (.../work_poiaddr/mkgmap) (revision 271) @@ -0,0 +1,196 @@ +/* + * 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.build; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.Vector; +import uk.me.parabola.mkgmap.general.MapPoint; + +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>(); + + public static final long POS_HASH_DIV = 8000; // the smaller -> more tiles + public static final long POS_HASH_MUL = 10000; // multiplicator for latitude to create hash + + 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); + + points.add(p); + + long posHash = getPosHashVal(p.getLocation().getLatitude(), p.getLocation().getLongitude()); + + //System.out.println("PosHash " + posHash); + + 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(points.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; + } + + 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/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/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (.../work_poiaddr/mkgmap) (revision 271) @@ -271,6 +271,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 271) +++ src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/mkgmap/main/MapMaker.java (.../work_poiaddr/mkgmap) (revision 271) @@ -277,26 +277,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/MapElement.java =================================================================== --- src/uk/me/parabola/mkgmap/general/MapElement.java (.../upstream/mkgmap) (revision 271) +++ src/uk/me/parabola/mkgmap/general/MapElement.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ src/uk/me/parabola/mkgmap/general/MapPoint.java (.../work_poiaddr/mkgmap) (revision 271) @@ -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: build.xml =================================================================== --- build.xml (.../upstream/mkgmap) (revision 271) +++ build.xml (.../work_poiaddr/mkgmap) (revision 271) @@ -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/LocatorConfig.xml =================================================================== --- resources/LocatorConfig.xml (.../upstream/mkgmap) (revision 0) +++ resources/LocatorConfig.xml (.../work_poiaddr/mkgmap) (revision 271) @@ -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 271) +++ resources/help/en/options (.../work_poiaddr/mkgmap) (revision 271) @@ -134,6 +134,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.