Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2017.
 *
 * 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.dem;

import java.util.ArrayList;
import java.util.List;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.reader.hgt.HGTConverter;

public class DEMSection {
        private static final Logger log = Logger.getLogger(DEMSection.class);
        private static final int STD_DIM = 64;
        private byte unknown1 = 0;
        private final int zoomLevel;
        private final boolean lastLevel;
        private final int pointsPerLat = STD_DIM;
        private final int pointsPerLon = STD_DIM;
        private final int nonStdHeight;
        private final int nonStdWidth;
        private final short flags1 = 0;
        private final int tilesLat;
        private final int tilesLon;
        private int recordDesc;
        private int tileDescSize;
        private int dataOffset;
        private int dataOffset2;
        private final int pointsDistanceLat;
        private final int pointsDistanceLon;
        private final int top;
        private final int left;
        private boolean hasExtra;
        private int minHeight = Integer.MAX_VALUE;
        private int maxHeight = Integer.MIN_VALUE;
        private List<DEMTile> tiles = new ArrayList<>();

        /**
         * Calculate the DEM data for the given position and resolution.
         * @param zoomLevel the zoom level
         * @param areaTop latitude of upper left corner in DEM units
         * @param areaLeft longitude of upper left corner in DEM units
         * @param areaHeight height in DEM units
         * @param areaWidth width in DEM units
         * @param hgtConverter the hgt converter
         * @param pointDist distance in DEM units between to height samples  
         * @param lastLevel: set to true to signal that readers are no longer needed for further levels
         */

        public DEMSection(int zoomLevel, int areaTop, int areaLeft, int areaHeight, int areaWidth,
                        HGTConverter hgtConverter, int pointDist, boolean lastLevel) {
                this.zoomLevel = zoomLevel;
                this.lastLevel = lastLevel;
               
                this.top = areaTop;
                this.left = areaLeft;

                // calculate raster that starts at top left corner
                // last row and right column have non-standard height / row values
                pointsDistanceLat = pointDist;
                pointsDistanceLon = pointDist;

                // allow automatic selection of interpolation method for each zoom level
                hgtConverter.startNewLevel(pointDist);

                int[] latInfo = getTileInfo(areaHeight, pointsDistanceLat);
                int[] lonInfo = getTileInfo(areaWidth, pointsDistanceLon);
                // store the values written to the header
                tilesLat = latInfo[0];
                tilesLon = lonInfo[0];
                nonStdHeight = latInfo[1];
                nonStdWidth = lonInfo[1];
                log.info("calculating zoom level:",zoomLevel,", dist:",pointDist,tilesLon,"x",tilesLat,"std tiles, nonstd x/y",nonStdWidth,"/",nonStdHeight);
                calcTiles(hgtConverter);
        }

        /**
         * Calculate the number of rows / columns and the non-standard height/width
         * @param demPoints number of 32 bit points
         * @param demDist distance between two sample points
         * @return array with dimension and non standard value normalised to 1 .. 95
         */

        private int[] getTileInfo(int demPoints, int demDist) {
                int resolution = STD_DIM * demDist;
                demPoints += demDist; // probably not needed, but Garmin seems to prefer large overlaps
                int nFull = demPoints / resolution;
                int rest = demPoints - nFull * resolution;
                int num = nFull;
                int nonstd = rest / demDist;
                if (rest % demDist != 0)
                        ++nonstd;
                // normalise non std value so that it is between 1 .. 95 because Garmin does it also
                if (nonstd >= STD_DIM / 2) {
                        ++num;
                } else {
                        if (num > 0)
                                nonstd += STD_DIM;
                }
                if (num == 0)
                        num = 1;
                int[] res = {num, nonstd};
                return res;
        }
       
       
        private void calcTiles(HGTConverter hgtConverter) {
                int resLon = pointsPerLon * pointsDistanceLon;
                int resLat = pointsPerLat * pointsDistanceLat;
                int latOff;
                int lonOff;
                int dataLen = 0;
                int minBaseHeight = Integer.MAX_VALUE;
                int maxBaseHeight = Integer.MIN_VALUE;
                int maxDeltaHeight = Integer.MIN_VALUE;
       
                for (int m = 0; m < tilesLat; m++) {
                        latOff = top - m * resLat;
                       
                        int height = pointsPerLat;
                        if (m + 1 == tilesLat) {
                                height = nonStdHeight;
                        }
                        for (int n = 0; n < tilesLon; n++) {
                                lonOff = left + n * resLon;
                                int width = pointsPerLon;
                                if (n + 1 == tilesLon) {
                                        width = nonStdWidth;
                                }
                                short[] realHeights = hgtConverter.getHeights(latOff, lonOff, height, width);
                                DEMTile tile;
                                tile = new DEMTile(n, m, width, height, realHeights);
                                tiles.add(tile);
                                if (tile.getEncodingType() != 0)
                                        hasExtra = true;
                                if (tile.hasValidHeights()) {
                                        if (tile.getBaseHeight() < minBaseHeight)
                                                minBaseHeight = tile.getBaseHeight();
                                        if (tile.getBaseHeight() > maxBaseHeight)
                                                maxBaseHeight = tile.getBaseHeight();
                                        if (tile.getMaxHeight() > maxHeight)
                                                maxHeight = tile.getMaxHeight();
                                        if (tile.getMaxDeltaHeight() > maxDeltaHeight)
                                                maxDeltaHeight = tile.getMaxDeltaHeight();
                                }
                                dataLen += tile.getBitStreamLen();
                        }
                        if (lastLevel) {
                                hgtConverter.freeMem();
                        }
                }
               
                hgtConverter.printStat();

                if (dataLen > 0) {
                        minHeight = minBaseHeight;
                } else {
                        minHeight = 0;
                        maxHeight = 0;
                }
                int deltaSize = (maxDeltaHeight <= 255) ? 1 : 2;
                int baseSize = (-128 < minBaseHeight && maxBaseHeight < 128) ? 1 : 2;
                int offsetSize = Utils.numberToPointerSize(dataLen);
               
                tileDescSize = offsetSize + baseSize + deltaSize + (hasExtra ? 1:0);
                recordDesc = offsetSize -1; // 0..3
                if (baseSize > 1)
                        recordDesc |= (1 << 2);
                if (deltaSize > 1)
                        recordDesc |= (1 << 3);
                if (hasExtra)
                        recordDesc |=  (1 << 4);
               
        }

        public void writeHeader(ImgFileWriter writer) {
                writer.put(unknown1);   //0x00
                writer.put1(zoomLevel); //0x01
                writer.putInt(pointsPerLat);    //0x02
                writer.putInt(pointsPerLon);    //0x06
                writer.putInt(nonStdHeight - 1);        //0x0A
                writer.putInt(nonStdWidth - 1); //0x0E
                writer.put2(flags1);    //0x12
                writer.putInt(tilesLon - 1);    //0x14
                writer.putInt(tilesLat - 1);    //0x18
               
                writer.put2(recordDesc);        //0x1c
                writer.put2(tileDescSize);      //0x1e
                writer.putInt(dataOffset);      //0x20
                writer.putInt(dataOffset2);     //0x24
                writer.putInt(left);    //0x28
                writer.putInt(top);     //0x2c
                writer.putInt(pointsDistanceLat);       //0x30
                writer.putInt(pointsDistanceLon);       //0x34
                assert minHeight >= Short.MIN_VALUE && minHeight <= Short.MAX_VALUE;
                writer.putChar((char) minHeight);       //0x38
                assert maxHeight >= Short.MIN_VALUE && maxHeight <= Short.MAX_VALUE;
                writer.putChar((char) maxHeight);       //0x3a
        }

        public void writeRest(ImgFileWriter writer) {
                dataOffset = writer.position();

                int off = 0;
                for (DEMTile tile : tiles) {
                        tile.setOffset(off);
                        tile.writeHeader(writer, recordDesc);
                        off += tile.getBitStreamLen();
                }
                dataOffset2 = writer.position();
                for (DEMTile tile : tiles) {
                        tile.writeBitStreamData(writer);
                }
        }
}