/* * Copyright (C) 2009. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * 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. */ package uk.me.parabola.imgfmt.app.mdr; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.List; import uk.me.parabola.imgfmt.app.ImgFileWriter; import uk.me.parabola.imgfmt.app.srt.MultiSortKey; import uk.me.parabola.imgfmt.app.srt.Sort; import uk.me.parabola.imgfmt.app.srt.SortKey; /** * Section containing cities. * * We need: map number, city index in map, offset in LBL, flags * and pointer into MDR 15 for the string name. * * @author Steve Ratcliffe */ public class Mdr5 extends MdrMapSection { private List<Mdr5Record> allCities = new ArrayList<>(); private List<Mdr5Record> cities = new ArrayList<>(); private int maxCityIndex; private int localCitySize; public Mdr5(MdrConfig config) { setConfig(config); } public void addCity(Mdr5Record record) { assert record.getMapIndex() != 0; allCities.add(record); if (record.getCityIndex() > maxCityIndex) maxCityIndex = record.getCityIndex(); } /** * Called after all cities to sort and number them. */ public void preWriteImpl() { localCitySize = numberToPointerSize(maxCityIndex + 1); List<SortKey<Mdr5Record>> sortKeys = new ArrayList<>(allCities.size()); Sort sort = getConfig().getSort(); for (Mdr5Record m : allCities) { if (m.getName() == null) continue; // Sort by city name, region name, country name and map index. SortKey<Mdr5Record> sortKey = sort.createSortKey(m, m.getName()); SortKey<Mdr5Record> regionKey = sort.createSortKey(null, m.getRegionName()); SortKey<Mdr5Record> countryKey = sort.createSortKey(null, m.getCountryName(), m.getMapIndex()); sortKey = new MultiSortKey<>(sortKey, regionKey, countryKey); sortKeys.add(sortKey); } Collections.sort(sortKeys); Collator collator = getConfig().getSort().getCollator(); int count = 0; Mdr5Record lastCity = null; // We need a common area to save the mdr20 values, since there can be multiple // city records with the same global city index int[] mdr20s = new int[sortKeys.size()+1]; int mdr20count = 0; for (SortKey<Mdr5Record> key : sortKeys) { Mdr5Record c = key.getObject(); c.setMdr20set(mdr20s); if (!c.isSameByName(collator, lastCity)) mdr20count++; c.setMdr20Index(mdr20count); if (c.isSameByMapAndName(collator, lastCity)) { c.setGlobalCityIndex(count); } else { count++; c.setGlobalCityIndex(count); cities.add(c); lastCity = c; } } } public void writeSectData(ImgFileWriter writer) { int size20 = getSizes().getMdr20Size(); Mdr5Record lastCity = null; boolean hasString = hasFlag(0x8); boolean hasRegion = hasFlag(0x4); Collator collator = getConfig().getSort().getCollator(); for (Mdr5Record city : cities) { int gci = city.getGlobalCityIndex(); addIndexPointer(city.getMapIndex(), gci); // Work out if the name is the same as the previous one and set // the flag if so. int flag = 0; int mapIndex = city.getMapIndex(); int region = city.getRegionIndex(); // Set the no-repeat flag if the name/region is different if (!city.isSameByName(collator, lastCity)) { flag = 0x800000; lastCity = city; } // Write out the record putMapIndex(writer, mapIndex); putLocalCityIndex(writer, city.getCityIndex()); writer.put3(flag | city.getLblOffset()); if (hasRegion) writer.putChar((char) region); if (hasString) putStringOffset(writer, city.getStringOffset()); putN(writer, size20, city.getMdr20()); } } /** * Put the map city index. This is the index within the individual map * and not the global city index used in mdr11. */ private void putLocalCityIndex(ImgFileWriter writer, int cityIndex) { if (localCitySize == 2) // 3 probably not possible in actual maps. writer.putChar((char) cityIndex); else writer.put((byte) cityIndex); } /** * Base size of 8, plus enough bytes to represent the map number * and the city number. * @return The size of a record in this section. */ public int getItemSize() { PointerSizes sizes = getSizes(); int size = sizes.getMapSize() + localCitySize + 3 + sizes.getMdr20Size(); if (hasFlag(0x4)) size += 2; if (hasFlag(0x8)) size += sizes.getStrOffSize(); return size; } protected int numberOfItems() { return cities.size(); } /** * Known structure: * bits 0-1: size of local city index - 1 (all values appear to work) * bit 3: has region * bit 4: has string * @return The value to be placed in the header. */ public int getExtraValue() { int val = (localCitySize - 1); // String offset is only included for a mapsource index. if (isForDevice()) { val |= 0x40; // not known, probably refers to mdr17. } else { val |= 0x04; // region val |= 0x08; // string } val |= 0x10; val |= 0x100; // mdr20 present return val; } protected void releaseMemory() { allCities = null; cities = null; } /** * Get a list of all the cities, including duplicate named ones. * @return All cities. */ public List<Mdr5Record> getCities() { return Collections.unmodifiableList(allCities); } public List<Mdr5Record> getSortedCities() { return Collections.unmodifiableList(cities); } }