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