de.nx42.maps4cim.map.ReliefMap.java Source code

Java tutorial

Introduction

Here is the source code for de.nx42.maps4cim.map.ReliefMap.java

Source

/**
 * maps4cim - a real world map generator for CiM 2
 * Copyright 2013 Sebastian Straub
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.nx42.maps4cim.map;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import com.google.common.io.LittleEndianDataOutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.nx42.maps4cim.map.ex.MapGeneratorException;
import de.nx42.maps4cim.map.ex.ReliefProcessingException;

/**
 * The ReliefMap describes the elevations of the resulting map.
 *
 * Each map has an ingame size of 8x8 km or a quadratic size of 64 km^2.
 * The ReliefMap contains a two-dimensional matrix with values ranging from
 * -1048.575 to +1048.576 meters, which describe the height of each single
 * control point of the map.
 *
 * Each map holds a raster of 2048x2048 equally sized fields. The ReliefMap
 * contains 2049x2049 entries, where each entry defines a control point
 * located in the south-west corner of a field (that's why there is one more
 * row and column for the north and the east edge).
 *
 * Values are represented in the ReliefMap as floats, which describe the metric
 * ingame elevations. In the final map, they are converted to 32bit signed
 * integers (little Endian), rounded to full millimeters (this is the CiM 2
 * map format).
 *
 * The design of the elevations in CiM 2 has some serious drawbacks that cannot
 * be circumvented:
 * 1. no water above 0m. This means no rivers and no lakes above sea level
 * 2. only one height per square. This means no caves or cliffs with extreme angles
 *
 * @author Sebastian Straub <sebastian-straub@gmx.net>
 */
public abstract class ReliefMap {

    private static Logger log = LoggerFactory.getLogger(ReliefMap.class);

    protected static final int edgeLength = 2049;
    protected static final int byteAmount = 2049 * 2049 * 4;

    /** minimum allowed value (internal, millimeters) */
    protected static final int minVal = -1048575;
    /** maximum allowed value (internal, millimeters) */
    protected static final int maxVal = 1048576;
    /** minimum allowed value (meters) */
    protected static final float minValF = -1048.575f;
    /** maximum allowed value (meters) */
    protected static final float maxValF = 1048.576f;

    /*
     * Factsheet:
     * - 2049x2049 float array
     * - allowed values: [- 1048.575; + 1048.576]
     * - left to right // west to east // x achsis
     * - bottom to top // south to north // y achsis
     * - float[y][x]
     * - float[0][0] = bottom left = southwest
     * - float[2048][2048] = top right = northeast
     */

    /**
     * Generates the full relief map as 2d-array, each data point representing
     * a control point of the map, in meters.
     *
     * Only this function must be overriden by implementations.
     *
     * @return the relief map, as 2d float array
     * @throws ReliefProcessingException if anything goes wrong (please wrap
     * other exceptions in this one)
     */
    public abstract float[][] generateRelief() throws ReliefProcessingException;

    /**
     * Generates the relief map by calling {@link ReliefMap#generateRelief()},
     * converts it to the CiM 2 map format and writes it to the specified output
     * stream
     * @param out the output stream to write the relief map into
     * @throws MapGeneratorException when the relief cannot be generated
     * @throws IOException when the resulting map can't be written
     */
    public void writeTo(OutputStream out) throws MapGeneratorException, IOException {
        float[][] reliefMap = generateRelief();

        log.info("Storing relief in native CiM2-Map format");
        storeByteStream(out, reliefMap);
        log.debug("Relief was written to file");
    }

    /**
     * Stores the relief map in the specified output stream, transforming
     * the floats to little endian 32 bit integers.
     * @param out the output stream to write the relief map into
     * @param reliefMap the relief map to write
     * @throws MapGeneratorException when the size of the relief map is invalid
     * @throws IOException when the resulting map can't be written
     */
    protected void storeByteStream(OutputStream out, float[][] reliefMap)
            throws MapGeneratorException, IOException {

        if (isValidSize(reliefMap)) {
            // byte outputput stream, wrapped by a little endian integer processor
            ByteArrayOutputStream bos = new ByteArrayOutputStream(byteAmount);
            LittleEndianDataOutputStream dos = new LittleEndianDataOutputStream(bos);

            // pass the resulting integers to the little endian byte output stream
            for (int y = 0; y < edgeLength; y++) {
                for (int x = 0; x < edgeLength; x++) {
                    // convert the floats (meter) to integers (millimeter)
                    dos.writeInt((int) (reliefMap[y][x] * 1000));
                }
            }

            // write to the user defined output stream
            bos.writeTo(out);

            // close streams
            dos.close();
            bos.close();
        } else {
            throw new MapGeneratorException("The size of the relief map is invalid. "
                    + "Only Maps with 2049 * 2049 control points are allowed");
        }

    }

    /**
     * Checks, if all arrays in the relief map have the correct size
     * @param reliefMap the relief map to check
     * @return true, iff the wrapping array's and all contained array's length
     * equals the predefined edge length
     */
    protected static boolean isValidSize(float[][] reliefMap) {
        int lenY = reliefMap.length;
        int lenX = reliefMap[0].length;
        if (lenX == edgeLength && lenY == edgeLength) {
            for (float[] fs : reliefMap) {
                if (fs.length != edgeLength) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

}