Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2010.
 *
 * 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.mkgmap.combiners;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.FileNotWritableException;
import uk.me.parabola.imgfmt.FileSystemParam;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.MapTooBigException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.map.Map;
import uk.me.parabola.imgfmt.app.map.MapReader;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.Point;
import uk.me.parabola.imgfmt.app.trergn.Polygon;
import uk.me.parabola.imgfmt.app.trergn.Polyline;
import uk.me.parabola.imgfmt.app.trergn.Zoom;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.build.MapBuilder;
import uk.me.parabola.mkgmap.build.MapSplitter;
import uk.me.parabola.mkgmap.general.LevelInfo;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapPoint;
import uk.me.parabola.mkgmap.general.MapShape;
import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
import uk.me.parabola.mkgmap.srt.SrtTextReader;
import uk.me.parabola.util.EnhancedProperties;

/**
 * Build the overview map.  This is a low resolution map that covers the whole
 * of a map set.  It also contains polygons that correspond to the areas
 * covered by the individual map tiles.
 *
 * @author Steve Ratcliffe
 */

public class OverviewBuilder implements Combiner {
        Logger log = Logger.getLogger(OverviewBuilder.class);
        public static final String OVERVIEW_PREFIX = "ovm_";
        private OverviewMapDataSource overviewSource;
        private String areaName;
        private String overviewMapname;
        private String overviewMapnumber;
        private String outputDir;              
        private Integer codepage;
        private Integer encodingType;
        private List<String[]> copyrightMsgs = new ArrayList<>();
        private List<String[]> licenseInfos = new ArrayList<>();
        private LevelInfo[] wantedLevels;
        private Area bounds;
        private boolean hasBackground;
        private EnhancedProperties overviewProps = new EnhancedProperties();
        private int maxRes = 16; // we can write a 0x4a polygon for planet in res 16.

        public OverviewBuilder() {
                this.overviewSource = new OverviewMapDataSource();
        }

        public void init(CommandArgs args) {
                areaName = args.get("area-name", "Overview Map");
                overviewMapname = args.get("overview-mapname", "osmmap");
                overviewMapnumber = args.get("overview-mapnumber", "63240000");
               
                outputDir = args.getOutputDir();
                overviewProps = new EnhancedProperties(args.getProperties());
        }

        public void onMapEnd(FileInfo finfo) {
                if (!finfo.isImg())
                        return;

                try {
                        readFileIntoOverview(finfo);
                } catch (FileNotFoundException e) {
                        throw new MapFailedException("Could not read detail map " + finfo.getFilename(), e);
                }
        }

        public void onFinish() {
                if (!hasBackground) {
                        List<MapShape> shapes = overviewSource.getShapes();
                        int inx = shapes.size();
                        overviewSource.addBackground();
                        // for --order-by-decreasing-area, need the background to be first:
                        if (shapes.size() > inx) // something was added
                                shapes.add(0, shapes.remove(inx));
                }
                calcLevels();
                writeOverviewMap();
                bounds = overviewSource.getBounds();
                overviewSource = null; // release memory
        }

        @Override
        public String getFilename() {
                return Utils.joinPath(outputDir, overviewMapname, "img");
        }

        private void calcLevels() {
                if (wantedLevels == null)
                        setRes(maxRes);
                else {
                        // make sure that the wanted levels for the overview map
                        // can store the largest 0x4a polygon at level 0
                        int n = wantedLevels.length-1;
                        while (n > 0 && wantedLevels[n].getBits() > maxRes)
                                n--;
                        if (n > 0){
                                int l = 0;
                                while (n >= 0){
                                        wantedLevels[n] = new LevelInfo(l++, wantedLevels[n].getBits());
                                        n--;
                                }
                                wantedLevels = Arrays.copyOfRange(wantedLevels, 0, l);
                                overviewSource.setMapLevels(wantedLevels);
                        } else {
                                setRes(maxRes);
                        }
                }
        }

        /**
         * Adjust {@code maxRes} value.
         * @param detailTileBounds tile bounds (of ovm_ file)
         * @param tileName tile name
         */

        private int checkFixRes(Area detailTileBounds, String tileName) {
                int newMaxRes = maxRes;
                int maxSize = MapSplitter.MAX_DIVISION_SIZE << (24 - newMaxRes);
                int maxDimPoly = detailTileBounds.getMaxDimension();
                if (maxDimPoly > maxSize) {
                        int oldMaxRes = newMaxRes;
                        while (maxDimPoly > maxSize) {
                                newMaxRes--;
                                maxSize = 0xffff << (24 - newMaxRes);
                        }
                        final String msg = "Tile selection (0x4a) polygon for";
                        final String msg2;
                        if (tileName != null)
                                msg2 = "tile " + tileName;
                        else
                                msg2 = detailTileBounds.toString();
                        log.error(msg, msg2, "cannot be written in level 0 resolution", oldMaxRes + ", using", newMaxRes, "instead");
                }
                return newMaxRes;
        }

        /**
         * Write out the overview map.
         */

        private void writeOverviewMap() {
                if (overviewSource.mapLevels() == null)
                        return;
               
                MapBuilder mb = new MapBuilder(false, true);
                mb.setEnableLineCleanFilters(false);

                FileSystemParam params = new FileSystemParam();
                params.setMapDescription(areaName);
                mb.setCopyrights(creMsgList(copyrightMsgs));
                mb.setMapInfo(creMsgList(licenseInfos));
               
               
                try {
                        if (codepage == null){
                                codepage = 0; // should not happen
                        }
                        Sort sort = SrtTextReader.sortForCodepage(codepage);
                        Map map = Map.createMap(overviewMapname, outputDir, params, overviewMapnumber, sort, true);
                        map.config(overviewProps);
                        mb.config(overviewProps);

                        if (encodingType != null){
                                map.getLblFile().setEncoder(encodingType, codepage);
                        }
                        mb.makeMap(map, overviewSource);
                        map.close();
                } catch (FileExistsException e) {
                        throw new ExitException("Could not create overview map", e);
                } catch (FileNotWritableException e) {
                        throw new ExitException("Could not write to overview map", e);
                } catch (MapTooBigException e) {
                        throw new MapTooBigException(e.getMaxAllowedSize(),
                                        "The overview map is too big.",
                                        "Try reducing the highest overview resolution or reducing the amount of information included in the overview.");
                }
        }

        /**
         * Add an individual .img file to the overview map.
         *
         * @param finfo Information about an individual map.
         */

        private void readFileIntoOverview(FileInfo finfo) throws FileNotFoundException {
                MapReader mapReader = null;
                String filename = finfo.getFilename();
                if (codepage == null){
                        codepage = finfo.getCodePage();
                }
                if (codepage != finfo.getCodePage()){
                        Logger.defaultLogger.warn("Input file " + filename + " has different code page " + finfo.getCodePage());
                }

                try {
                        mapReader = new MapReader(filename);

                        if (encodingType == null){
                                encodingType = mapReader.getEncodingType();
                        }
                        if (encodingType != mapReader.getEncodingType()){
                                Logger.defaultLogger.warn("Input file " + filename + " has different charset type " + encodingType);
                        }

                        String[] msgs = mapReader.getCopyrights();
                        boolean found = false;
                        for (String[] block : copyrightMsgs) {
                                if (Arrays.deepEquals(block, msgs)){
                                        found = true;
                                        break;
                                }
                        }
                        if (!found )
                                copyrightMsgs.add(msgs);
                       
                        msgs = finfo.getLicenseInfo();
                        found = false;
                        for (String[] block : licenseInfos) {
                                if (Arrays.deepEquals(block, msgs)){
                                        found = true;
                                        break;
                                }
                        }
                        if (!found )
                                licenseInfos.add(msgs);
                       
                       
                        Zoom[] levels = mapReader.getLevels();
                        if (wantedLevels == null){
                                LevelInfo[] mapLevels;
                                if (isOverviewImg(filename)){
                                        mapLevels = new LevelInfo[levels.length-1];
                                        for (int i = 1; i < levels.length; i++){
                                                mapLevels[i-1] = new LevelInfo(levels[i].getLevel(), levels[i].getResolution());
                                        }
                                } else {
                                        mapLevels = new LevelInfo[1];
                                        mapLevels[0] = new LevelInfo(levels[1].getLevel(), levels[1].getResolution());
                                }
                                wantedLevels = mapLevels;
                                maxRes = wantedLevels[wantedLevels.length-1].getBits();
                        }
                        addMapCoverageArea(finfo);
                        if (isOverviewImg(filename)){
                                readPoints(mapReader);
                                readLines(mapReader);
                                readShapes(mapReader);
                        }
                } catch (FileNotFoundException e) {
                        throw new ExitException("Could not open " + filename + " when creating overview file");
                } finally {
                        Utils.closeFile(mapReader);
                }
        }

        /**
         * Read the points from the .img file and add them to the overview map.
         *
         * @param mapReader Map reader on the detailed .img file.
         */

        private void readPoints(MapReader mapReader) {
                Area sourceBounds = overviewSource.getBounds();
                Zoom[] levels = mapReader.getLevels();
                for (int l = 1; l < levels.length; l++){
                        int min = levels[l].getLevel();
                        int res = levels[l].getResolution();
                        List<Point> pointList = mapReader.pointsForLevel(min, MapReader.WITH_EXT_TYPE_DATA);
                        for (Point point: pointList) {
                                if (log.isDebugEnabled())
                                        log.debug("got point", point);
                                if (!sourceBounds.contains(point.getLocation())){
                                        if (log.isDebugEnabled())
                                                log.debug(point, "dropped, is outside of tile boundary");
                                        continue;
                                }
                                       
                                MapPoint mp = new MapPoint();
                                mp.setType(point.getType());
                                if (point.getLabel() != null) {
                                        mp.setName(point.getLabel().getText());
                                }
                                mp.setMaxResolution(res);
                                mp.setMinResolution(res);  
                                mp.setLocation(point.getLocation());
                                overviewSource.addPoint(mp);
                        }
                }
        }

        /**
         * Read the lines from the .img file and add them to the overview map.
         *
         * @param mapReader Map reader on the detailed .img file.
         */

        private void readLines(MapReader mapReader) {
                Zoom[] levels = mapReader.getLevels();
                for (int l = 1; l < levels.length; l++){
                        int min = levels[l].getLevel();
                        int res = levels[l].getResolution();
                        List<Polyline> lineList = mapReader.linesForLevel(min);
                        for (Polyline line : lineList) {
                                if (log.isDebugEnabled())
                                        log.debug("got line", line);
                                MapLine ml = new MapLine();

                                List<Coord> points = line.getPoints();
                                if (log.isDebugEnabled())                      
                                        log.debug("line point list", points);
                                if (points.size() < 2)
                                        continue;

                                ml.setType(line.getType());
                                if ((line.getType() & 0x40) != 0)
                                        ml.setDirection(true);
                                if (line.getLabel() != null)
                                        ml.setName(line.getLabel().getText());
                                ml.setMaxResolution(res);
                                ml.setMinResolution(res);  
                                ml.setPoints(points);

                                overviewSource.addLine(ml);
                        }
                }
        }

        /**
         * Read the polygons from the .img file and add them to the overview map.
         *
         * @param mapReader Map reader on the detailed .img file.
         */

        private void readShapes(MapReader mapReader) {
                Zoom[] levels = mapReader.getLevels();
                for (int l = 1; l < levels.length; l++){
                        int min = levels[l].getLevel();
                        int res = levels[l].getResolution();
                        List<Polygon> list = mapReader.shapesForLevel(min, MapReader.WITH_EXT_TYPE_DATA);
                        for (Polygon shape : list) {
                                if (log.isDebugEnabled())
                                        log.debug("got polygon", shape);
                                if (shape.getType() == 0x4b){
                                        hasBackground = true;
                                }
                                MapShape ms = new MapShape();

                                List<Coord> points = shape.getPoints();
                                if (log.isDebugEnabled())                      
                                        log.debug("polygon point list", points);

                                if (points.size() < 3)
                                        continue;

                                ms.setType(shape.getType());
                                if (shape.getLabel() != null)
                                        ms.setName(shape.getLabel().getText());
                                ms.setMaxResolution(res);
                                ms.setMinResolution(res);  
                                ms.setPoints(points);

                                overviewSource.addShape(ms);
                        }
                }
        }

        /**
         * Add an area that shows the area covered by a detailed map.  This can
         * be an arbitary shape, although at the current time we only support
         * rectangles.
         * Also build up full overview path (not ness. rectangle) for DEM
         * and check/decrease resolution if necessary
         *
         * @param finfo Information about a detail map.
         */

        private void addMapCoverageArea(FileInfo finfo) {
                Area bounds = finfo.getBounds();
                List<Coord> points = bounds.toCoords();
                points.forEach(overviewSource::addToBounds);
                overviewSource.addToTileAreaPath(points);
                maxRes = checkFixRes(bounds, finfo.getMapname());
                // Create the tile coverage rectangle
                MapShape bg = new MapShape();
                bg.setType(0x4a);
                bg.setPoints(points);
                bg.setMinResolution(0);
                bg.setName(finfo.getDescription() + '\u001d' + finfo.getMapname());
               
                overviewSource.addShape(bg);
        }

        public Area getBounds() {
                if (bounds != null)
                        return bounds;
                if (overviewSource != null)
                        return overviewSource.getBounds();
                return new Area(1, 1, -1, -1); // return invalid bbox
        }

        /**
         * Check if the the file name points to a partly overview img file  
         * @param name full path or just a name
         * @return true if the name points to a partly overview img file
         */

        public static boolean isOverviewImg (String name){
                return new File(name).getName().startsWith(OVERVIEW_PREFIX);
        }
        /**
         * Add the prefix to the file name.
         * @param name filename
         * @return filename of the corresponding overview img file (without a path)
         */

        public static String getOverviewImgName (String name){
                File f = new File(name);
                return OverviewBuilder.OVERVIEW_PREFIX + f.getName();
        }

        public static String getMapName(String name) {
                String fname = new File(name).getName();
                if (fname.startsWith(OVERVIEW_PREFIX))
                        return fname.substring(OVERVIEW_PREFIX.length());
                return name;
        }
       
        private static List<String> creMsgList(List<String[]> msgs){
                ArrayList< String> list = new ArrayList<>();
                for (int i = 0; i < msgs.size(); i++){
                        String[] block = msgs.get(i);
                        list.addAll(Arrays.asList(block));
                        if (i < msgs.size() - 1) {
                                // separate blocks
                                list.add("");
                        }
                }
                return list;
        }

        /**
         * Set the highest resolution
         * @param resolution
         */

        private void setRes(int resolution) {
                LevelInfo[] mapLevels = new LevelInfo[1];
                mapLevels[0] = new LevelInfo(0, resolution);
                overviewSource.setMapLevels(mapLevels);
        }

}