Subversion Repositories mkgmap

Rev

Rev 4376 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.TreeMap;

import uk.me.parabola.imgfmt.Utils;
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.
 */

public class PlacesFile {
        public static final int MIN_INDEXED_POI_TYPE = 0x29;
        public static final int MAX_INDEXED_POI_TYPE = 0x30;
        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 TreeMap<Integer, List<POIIndex>> poiIndex = new TreeMap<>();

        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) {
                countryList.forEach(c -> c.write(writer));
                placeHeader.endCountries(writer.position());

                regionList.forEach(r -> r.write(writer));
                placeHeader.endRegions(writer.position());

                cityList.forEach(sc -> sc.write(writer));

                placeHeader.endCity(writer.position());

                for (List<POIIndex> pil : poiIndex.values()) {
                        // 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);
                        }
                        sorted.sort(null);

                        for (SortKey<POIIndex> key : sorted) {
                                key.getObject().write(writer);
                        }
                }
                placeHeader.endPOIIndex(writer.position());

                int poistart = writer.position();
                int poiglobalflags = placeHeader.getPOIGlobalFlags();
                final int cityPtrSize = Utils.numberToPointerSize(cityList.size());
                final int zipPtrSize = Utils.numberToPointerSize(postalCodes.size());
                final int highwayPtrSize = Utils.numberToPointerSize(highways.size());
                final int exitFacilityPtrSize = Utils.numberToPointerSize(exitFacilities.size());
                for (POIRecord p : pois)
                        p.write(writer, poiglobalflags,
                                writer.position() - poistart, cityPtrSize, zipPtrSize, highwayPtrSize, exitFacilityPtrSize);
                placeHeader.endPOI(writer.position());

                int numPoiIndexEntries = 0;
                for (Entry<Integer, List<POIIndex>> entry : poiIndex.entrySet()) {
                        int i = entry.getKey();
                        writer.put1u(i);
                        writer.put3u(numPoiIndexEntries + 1);
                        numPoiIndexEntries += entry.getValue().size();
                }
                placeHeader.endPOITypeIndex(writer.position());

                zipList.forEach(z -> 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());

                exitFacilities.forEach(ef -> ef.write(writer));
                placeHeader.endExitFacility(writer.position());

                highways.forEach(h -> h.write(writer, true));
                placeHeader.endHighwayData(writer.position());
        }

        Country createCountry(String name, String abbr) {
                String s = abbr != null ? name + (char)0x1d + abbr : name;
                return countries.computeIfAbsent(s, k -> new Country(countries.size() + 1, lblFile.newLabel(s)));
        }

        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();
                return regions.computeIfAbsent(uniqueRegionName, k -> new Region(country, lblFile.newLabel(s)));
        }

        /**
         * Create city without region
         * @param country the country
         * @param name the name of the city
         * @param unique set to true if you needed a new city object
         * @return the city object
         */

        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) {
                        uniqueCityName = createUniqueCityName(uniqueCityName);
                }

                City c = null;
                if (!unique)
                        c = cities.get(uniqueCityName);
               
                if (c == null) {
                        c = new City(country);

                        c.setLabel(lblFile.newLabel(name));

                        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;
        }

        /**
         * Create city with region
         * @param region the region
         * @param name the name of the city
         * @param unique set to true if you needed a new city object
         * @return the city object
         */

        City createCity(Region region, String name, boolean unique) {
               
                String uniqueCityName = name.toUpperCase() + "_R" + region.getLabel().getOffset();
               
                if (unique) {
                        uniqueCityName = createUniqueCityName(uniqueCityName);
                }

                City c = null;
                if(!unique) {
                        c = cities.get(uniqueCityName);
                }
               
                if(c == null) {
                        c = new City(region);

                        c.setLabel(lblFile.newLabel(name));

                        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;
        }

        private String createUniqueCityName(String name) {
                String uniqueCityName = name;
                while (cities.get(uniqueCityName) != null) {
                        // add semi-random suffix.
                        uniqueCityName += "_" + random.nextInt(0x10000);
                }
                return uniqueCityName;
        }
       
        Zip createZip(String code) {
                return postalCodes.computeIfAbsent(code, k -> new Zip(lblFile.newLabel(code)));
        }

        Highway createHighway(Region region, String name) {
                Highway h = new Highway(region, highways.size()+1);

                h.setLabel(lblFile.newLabel(name));

                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;
                POIRecord p = new POIRecord();

                p.setLabel(lblFile.newLabel(name));
                pois.add(p);
               
                return p;
        }

        POIRecord createExitPOI(String name, Exit exit) {
                assert !poisClosed;
                POIRecord p = new POIRecord();

                p.setLabel(lblFile.newLabel(name));
                p.setExit(exit);
                pois.add(p);

                return p;
        }

        void createPOIIndex(String name, int index, Subdivision group, int type) {
                assert index < 0x100 : "Too many POIS in division";
                int t = type >> 8;
                if (t < MIN_INDEXED_POI_TYPE || t > MAX_INDEXED_POI_TYPE)
                        return;
               
                POIIndex pi = new POIIndex(name, index, group, type & 0xff);
                poiIndex.computeIfAbsent(t, k -> new ArrayList<>()).add(pi);
        }

        void allPOIsDone() {
                sortCountries();
                sortRegions();
                sortCities();
                sortZips();

                poisClosed = true;

                int poiFlags = 0;
                for (POIRecord p : pois) {
                        poiFlags |= p.getPOIFlags();
                }
                placeHeader.setPOIGlobalFlags(poiFlags);

                int ofs = 0;
                final int cityPtrSize = Utils.numberToPointerSize(cityList.size());
                final int zipPtrSize = Utils.numberToPointerSize(postalCodes.size());
                final int highwayPtrSize = Utils.numberToPointerSize(highways.size());
                final int exitFacilityPtrSize = Utils.numberToPointerSize(exitFacilities.size());
                for (POIRecord p : pois)
                        ofs += p.calcOffset(ofs, poiFlags, cityPtrSize, zipPtrSize, highwayPtrSize, exitFacilityPtrSize);
        }

        /**
         * I don't know that you have to sort these (after almost all 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);
                }
                keys.sort(null);

                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 : regions.values()) {
                        SortKey<Region> key = sort.createSortKey(r, r.getLabel(), r.getCountry().getIndex());
                        keys.add(key);
                }
                keys.sort(null);

                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);
                }
                keys.sort(null);

                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);
                }
                keys.sort(null);

                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 int numHighways() {
                return highways.size();
        }

        public int numExitFacilities() {
                return exitFacilities.size();
        }

        public void setSort(Sort sort) {
                this.sort = sort;
        }
}