/* * Copyright (C) 2006 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: 03-Dec-2006 */ package uk.me.parabola.imgfmt.app.trergn; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import uk.me.parabola.imgfmt.app.Area; import uk.me.parabola.imgfmt.app.BufferedImgFileWriter; import uk.me.parabola.imgfmt.app.ImgFile; import uk.me.parabola.imgfmt.app.ImgFileWriter; import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.labelenc.EncodedText; import uk.me.parabola.imgfmt.fs.ImgChannel; import uk.me.parabola.log.Logger; import uk.me.parabola.util.Configurable; import uk.me.parabola.util.EnhancedProperties; /** * This is the file that contains the overview of the map. There * can be different zoom levels and each level of zoom has an * associated set of subdivided areas. Each of these areas then points * into the RGN file. * * This is quite a complex file as there are quite a few miscellaneous pieces * of information stored. * * @author Steve Ratcliffe */ public class TREFile extends ImgFile implements Configurable { private static final Logger log = Logger.getLogger(TREFile.class); // Zoom levels for map // private List<Zoom> mapLevels = new ArrayList<Zoom>(); private final Zoom[] mapLevels = new Zoom[16]; private final List<Label> copyrights = new ArrayList<>(); // Information about polylines. eg roads etc. private final List<PolylineOverview> polylineOverviews = new ArrayList<PolylineOverview>(); private final List<PolygonOverview> polygonOverviews = new ArrayList<PolygonOverview>(); private final List<PointOverview> pointOverviews = new ArrayList<PointOverview>(); private int lastRgnPos; private final TREHeader header = new TREHeader(); public TREFile(ImgChannel chan) { setHeader(header); setWriter(new BufferedImgFileWriter(chan)); // Position at the start of the writable area. position(header.getHeaderLength()); } public Zoom createZoom(int zoom, int bits) { Zoom z = new Zoom(zoom, bits); mapLevels[zoom] = z; return z; } /** * Add a string to the 'mapinfo' section. This is a section between the * header and the start of the data. Nothing points to it directly. * * @param enc A string in the EncodedText format. */ public void addInfo(EncodedText enc) { byte[] val = enc.getCtext(); if (position() != header.getHeaderLength() + header.getMapInfoSize()) throw new IllegalStateException("All info must be added before anything else"); header.setMapInfoSize(header.getMapInfoSize() + enc.getLength() + 1); getWriter().put(val); getWriter().put((byte) 0); } public void addCopyright(Label cr) { copyrights.add(cr); } public void addPointOverview(PointOverview ov) { pointOverviews.add(ov); } public void addPolylineOverview(PolylineOverview ov) { polylineOverviews.add(ov); } public void addPolygonOverview(PolygonOverview ov) { polygonOverviews.add(ov); } public void config(EnhancedProperties props) { header.config(props); } /** * Write out the body of the TRE file. The act of writing the body sections * out provides us with pointers that are needed for the header. Therefore * the header needs to be written after the body (or obviously we could * make two passes). */ private void writeBody(boolean includeExtendedTypeData) { writeMapLevels(); writeSubdivs(); writeCopyrights(); writeOverviews(); if(includeExtendedTypeData) { writeExtTypeOffsetsRecords(); writeExtTypeOverviews(); } } /** * Write out the subdivisions. This is quite complex as they have to be * numbered and written out keeping their parent/child relationship * intact. */ private void writeSubdivs() { header.setSubdivPos(position()); int subdivnum = 1; // numbers start at one // First prepare to number them all for (int i = 15; i >= 0; i--) { Zoom z = mapLevels[i]; if (z == null) continue; Iterator<Subdivision> it = z.subdivIterator(); while (it.hasNext()) { Subdivision sd = it.next(); log.debug("setting number to", subdivnum); sd.setNumber(subdivnum++); } } // Now we can write them all out. for (int i = 15; i >= 0; i--) { Zoom z = mapLevels[i]; if (z == null) continue; Iterator<Subdivision> it = z.subdivIterator(); while (it.hasNext()) { Subdivision sd = it.next(); sd.write(getWriter()); if (sd.hasNextLevel()) header.setSubdivSize(header.getSubdivSize() + TREHeader.SUBDIV_REC_SIZE2); else header.setSubdivSize(header.getSubdivSize() + TREHeader.SUBDIV_REC_SIZE); } } getWriter().putInt(lastRgnPos); header.setSubdivSize(header.getSubdivSize() + 4); } private void writeExtTypeOffsetsRecords() { header.setExtTypeOffsetsPos(position()); Subdivision sd = null; for (int i = 15; i >= 0; i--) { Zoom z = mapLevels[i]; if (z == null) continue; Iterator<Subdivision> it = z.subdivIterator(); while (it.hasNext()) { sd = it.next(); sd.writeExtTypeOffsetsRecord(getWriter()); header.incExtTypeOffsetsSize(); } } if(sd != null) { sd.writeLastExtTypeOffsetsRecord(getWriter()); header.incExtTypeOffsetsSize(); } } /** * Write out the map levels. This is a mapping between the level number * and the resolution. */ private void writeMapLevels() { // Write out the map levels (zoom) header.setMapLevelPos(position()); for (int i = 15; i >= 0; i--) { // They need to be written in reverse order I think Zoom z = mapLevels[i]; if (z == null) continue; header.setMapLevelsSize(header.getMapLevelsSize() + TREHeader.MAP_LEVEL_REC_SIZE); z.write(getWriter()); } } /** * Write out the overview section. This is a mapping between the map feature * type and the highest level (lowest detail) that it appears at. There * are separate ones for points, lines and polygons. */ private void writeOverviews() { header.setPointPos(position()); // Point overview section Collections.sort(pointOverviews); for (Overview ov : pointOverviews) { if(!ov.hasExtType()) { ov.setMaxLevel(decodeLevel(ov.getMinResolution())); ov.write(getWriter()); header.incPointSize(); } } // Line overview section. header.setPolylinePos(position()); Collections.sort(polylineOverviews); for (Overview ov : polylineOverviews) { if(!ov.hasExtType()) { ov.setMaxLevel(decodeLevel(ov.getMinResolution())); ov.write(getWriter()); header.incPolylineSize(); } } // Polygon overview section header.setPolygonPos(position()); Collections.sort(polygonOverviews); for (Overview ov : polygonOverviews) { if(!ov.hasExtType()) { ov.setMaxLevel(decodeLevel(ov.getMinResolution())); ov.write(getWriter()); header.incPolygonSize(); } } } private void writeExtTypeOverviews() { header.setExtTypeOverviewsPos(position()); // assumes overviews are already sorted for (Overview ov : polylineOverviews) { if(ov.hasExtType()) { ov.setMaxLevel(decodeLevel(ov.getMinResolution())); ov.write(getWriter()); header.incExtTypeOverviewsSize(); header.incNumExtTypeLineTypes(); } } for (Overview ov : polygonOverviews) { if(ov.hasExtType()) { ov.setMaxLevel(decodeLevel(ov.getMinResolution())); ov.write(getWriter()); header.incExtTypeOverviewsSize(); header.incNumExtTypeAreaTypes(); } } for (Overview ov : pointOverviews) { if(ov.hasExtType()) { ov.setMaxLevel(decodeLevel(ov.getMinResolution())); ov.write(getWriter()); header.incExtTypeOverviewsSize(); header.incNumExtTypePointTypes(); } } } /** * Convert a min resolution to a level. We return the lowest level (most * detailed) that has a resolution less than or equal to the given resolution. * * @param minResolution The minimum resolution. * @return The level corresponding to the resolution. */ private int decodeLevel(int minResolution) { Zoom top = null; for (int i = 15; i >= 0; i--) { Zoom z = mapLevels[i]; if (z == null) continue; if (top == null) top = z; if (z.getResolution() >= minResolution) return z.getLevel(); } // If not found, then allow it only at the top level if (top != null) return top.getLevel(); else return 0; // Fail safe, shouldn't really happen } /** * Write out the copyrights. This is just a list of pointers to strings * in the label section basically. */ private void writeCopyrights() { // Write out the pointers to the labels that hold the copyright strings header.setCopyrightPos(position()); ImgFileWriter writer = getWriter(); for (Label l : copyrights) { header.incCopyrightSize(); writer.put3(l.getOffset()); } } public void setLastRgnPos(int lastRgnPos) { TREFile.this.lastRgnPos = lastRgnPos; } public void write(boolean includeExtendedTypeData) { // Do anything that is in structures and that needs to be dealt with. writeBody(includeExtendedTypeData); } public void writePost() { // Now refresh the header position(0); getHeader().writeHeader(getWriter()); } public void setMapId(int mapid) { header.setMapId(mapid); } public void setBounds(Area area) { header.setBounds(area); } public void addPoiDisplayFlags(byte b) { header.addPoiDisplayFlags(b); } public void setDriveOnLeft(boolean b) { header.setDriveOnLeft(b); } }