/* * Copyright (C) 2007 Steve Ratcliffe * * 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. * * * Author: Steve Ratcliffe * Create date: Jan 1, 2008 */ package uk.me.parabola.imgfmt.app.lbl; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; import uk.me.parabola.imgfmt.app.Exit; import uk.me.parabola.imgfmt.app.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.srt.CombinedSortKey; import uk.me.parabola.imgfmt.app.srt.Sort; import uk.me.parabola.imgfmt.app.srt.SortKey; import uk.me.parabola.imgfmt.app.trergn.Subdivision; /** * This is really part of the LBLFile. We split out all the parts of the file * that are to do with location to here. */ @SuppressWarnings({"unchecked", "rawtypes"}) public class PlacesFile { private final Map<String, Country> countries = new LinkedHashMap<>(); private final List<Country> countryList = new ArrayList<>(); private final Map<String, Region> regions = new LinkedHashMap<>(); private final List<Region> regionList = new ArrayList<>(); private final Map<String, City> cities = new LinkedHashMap<>(); private final List<City> cityList = new ArrayList<>(); private final Map<String, Zip> postalCodes = new LinkedHashMap<>(); private final List<Zip> zipList = new ArrayList<>(); private final List<Highway> highways = new ArrayList<>(); private final List<ExitFacility> exitFacilities = new ArrayList<>(); private final List<POIRecord> pois = new ArrayList<>(); private final List[] poiIndex = new ArrayList[256]; private LBLFile lblFile; private PlacesHeader placeHeader; private boolean poisClosed; private Sort sort; private final Random random = new Random(); /** * We need to have links back to the main LBL file and need to be passed * the part of the header that we manage here. * * @param file The main LBL file, used so that we can create labels. * @param pheader The place header. */ void init(LBLFile file, PlacesHeader pheader) { lblFile = file; placeHeader = pheader; } void write(ImgFileWriter writer) { for (Country c : countryList) c.write(writer); placeHeader.endCountries(writer.position()); for (Region region : regionList) region.write(writer); placeHeader.endRegions(writer.position()); for (City sc : cityList) sc.write(writer); placeHeader.endCity(writer.position()); for (List<POIIndex> pil : poiIndex) { if(pil != null) { // sort entries by POI name List<SortKey<POIIndex>> sorted = new ArrayList<>(); for (POIIndex index : pil) { SortKey<POIIndex> sortKey = sort.createSortKey(index, index.getName()); sorted.add(sortKey); } Collections.sort(sorted); for (SortKey<POIIndex> key : sorted) { key.getObject().write(writer); } } } placeHeader.endPOIIndex(writer.position()); int poistart = writer.position(); byte poiglobalflags = placeHeader.getPOIGlobalFlags(); for (POIRecord p : pois) p.write(writer, poiglobalflags, writer.position() - poistart, cityList.size(), postalCodes.size(), highways.size(), exitFacilities.size()); placeHeader.endPOI(writer.position()); int numPoiIndexEntries = 0; for (int i = 0; i < 256; ++i) { if(poiIndex[i] != null) { writer.put((byte)i); writer.put3(numPoiIndexEntries + 1); numPoiIndexEntries += poiIndex[i].size(); } } placeHeader.endPOITypeIndex(writer.position()); for (Zip z : zipList) z.write(writer); placeHeader.endZip(writer.position()); int extraHighwayDataOffset = 0; for (Highway h : highways) { h.setExtraDataOffset(extraHighwayDataOffset); extraHighwayDataOffset += h.getExtraDataSize(); h.write(writer, false); } placeHeader.endHighway(writer.position()); for (ExitFacility ef : exitFacilities) ef.write(writer); placeHeader.endExitFacility(writer.position()); for (Highway h : highways) h.write(writer, true); placeHeader.endHighwayData(writer.position()); } Country createCountry(String name, String abbr) { 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(s, c); } return c; } Region createRegion(Country country, String name, String abbr) { String s = abbr != null ? name + (char)0x1d + abbr : name; String uniqueRegionName = s.toUpperCase() + "_C" + country.getLabel().getOffset(); Region r = regions.get(uniqueRegionName); if(r == null) { r = new Region(country); Label l = lblFile.newLabel(s); r.setLabel(l); regionList.add(r); regions.put(uniqueRegionName, r); } return r; } City createCity(Country country, String name, boolean unique) { String uniqueCityName = name.toUpperCase() + "_C" + country.getLabel().getOffset(); // if unique is true, make sure that the name really is unique if(unique && cities.get(uniqueCityName) != null) { do { // add random suffix uniqueCityName += "_" + new Random().nextInt(0x10000); } while(cities.get(uniqueCityName) != null); } City c = null; if (!unique) c = cities.get(uniqueCityName); if (c == null) { c = new City(country); Label l = lblFile.newLabel(name); c.setLabel(l); cityList.add(c); cities.put(uniqueCityName, c); assert cityList.size() == cities.size() : " cityList and cities are different lengths after inserting " + name + " and " + uniqueCityName; } return c; } City createCity(Region region, String name, boolean unique) { String uniqueCityName = name.toUpperCase() + "_R" + region.getLabel().getOffset(); // if unique is true, make sure that the name really is unique if (unique && cities.get(uniqueCityName) != null) { do { // add semi-random suffix. uniqueCityName += "_" + random.nextInt(0x10000); } while(cities.get(uniqueCityName) != null); } City c = null; if(!unique) c = cities.get(uniqueCityName); if(c == null) { c = new City(region); Label l = lblFile.newLabel(name); c.setLabel(l); cityList.add(c); cities.put(uniqueCityName, c); assert cityList.size() == cities.size() : " cityList and cities are different lengths after inserting " + name + " and " + uniqueCityName; } return c; } Zip createZip(String code) { Zip z = postalCodes.get(code); if(z == null) { z = new Zip(); Label l = lblFile.newLabel(code); z.setLabel(l); zipList.add(z); postalCodes.put(code, z); } return z; } Highway createHighway(Region region, String name) { Highway h = new Highway(region, highways.size()+1); Label l = lblFile.newLabel(name); h.setLabel(l); highways.add(h); return h; } public ExitFacility createExitFacility(int type, char direction, int facilities, String description, boolean last) { Label d = lblFile.newLabel(description); ExitFacility ef = new ExitFacility(type, direction, facilities, d, last, exitFacilities.size()+1); exitFacilities.add(ef); return ef; } POIRecord createPOI(String name) { assert !poisClosed; // TODO... POIRecord p = new POIRecord(); Label l = lblFile.newLabel(name); p.setLabel(l); pois.add(p); return p; } POIRecord createExitPOI(String name, Exit exit) { assert !poisClosed; // TODO... POIRecord p = new POIRecord(); Label l = lblFile.newLabel(name); p.setLabel(l); p.setExit(exit); pois.add(p); return p; } POIIndex createPOIIndex(String name, int index, Subdivision group, int type) { assert index < 0x100 : "Too many POIS in division"; POIIndex pi = new POIIndex(name, (byte)index, group, (byte)type); int t = type >> 8; if(poiIndex[t] == null) poiIndex[t] = new ArrayList<POIIndex>(); poiIndex[t].add(pi); return pi; } void allPOIsDone() { sortCountries(); sortRegions(); sortCities(); sortZips(); poisClosed = true; byte poiFlags = 0; for (POIRecord p : pois) { poiFlags |= p.getPOIFlags(); } placeHeader.setPOIGlobalFlags(poiFlags); int ofs = 0; for (POIRecord p : pois) ofs += p.calcOffset(ofs, poiFlags, cityList.size(), postalCodes.size(), highways.size(), exitFacilities.size()); } /** * I don't know that you have to sort these (after all most tiles will * only be in one country or at least a very small number). * * But why not? */ private void sortCountries() { List<SortKey<Country>> keys = new ArrayList<>(); for (Country c : countries.values()) { SortKey<Country> key = sort.createSortKey(c, c.getLabel()); keys.add(key); } Collections.sort(keys); countryList.clear(); int index = 1; for (SortKey<Country> key : keys) { Country c = key.getObject(); c.setIndex(index++); countryList.add(c); } } /** * Sort the regions by the defined sort. */ private void sortRegions() { List<SortKey<Region>> keys = new ArrayList<>(); for (Region r : regionList) { SortKey<Region> key = sort.createSortKey(r, r.getLabel(), r.getCountry().getIndex()); keys.add(key); } Collections.sort(keys); regionList.clear(); int index = 1; for (SortKey<Region> key : keys) { Region r = key.getObject(); r.setIndex(index++); regionList.add(r); } } /** * Sort the cities by the defined sort. */ private void sortCities() { List<SortKey<City>> keys = new ArrayList<>(); for (City c : cityList) { SortKey<City> sortKey = sort.createSortKey(c, c.getLabel()); sortKey = new CombinedSortKey<>(sortKey, c.getRegionNumber(), c.getCountryNumber()); keys.add(sortKey); } Collections.sort(keys); cityList.clear(); int index = 1; for (SortKey<City> sc: keys) { City city = sc.getObject(); city.setIndex(index++); cityList.add(city); } } private void sortZips() { List<SortKey<Zip>> keys = new ArrayList<>(); for (Zip c : postalCodes.values()) { SortKey<Zip> sortKey = sort.createSortKey(c, c.getLabel()); keys.add(sortKey); } Collections.sort(keys); zipList.clear(); int index = 1; for (SortKey<Zip> sc: keys) { Zip zip = sc.getObject(); zip.setIndex(index++); zipList.add(zip); } } public int numCities() { return cityList.size(); } public int numZips() { return postalCodes.size(); } public void setSort(Sort sort) { this.sort = sort; } }