All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rgi.geopackage.features.WellKnownBinaryFactory Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Reinventing Geospatial, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.rgi.geopackage.features;

import com.rgi.geopackage.features.geometry.Geometry;
import com.rgi.geopackage.features.geometry.GeometryFactory;
import com.rgi.geopackage.features.geometry.m.WkbGeometryCollectionM;
import com.rgi.geopackage.features.geometry.m.WkbGeometryM;
import com.rgi.geopackage.features.geometry.m.WkbLineStringM;
import com.rgi.geopackage.features.geometry.m.WkbMultiLineStringM;
import com.rgi.geopackage.features.geometry.m.WkbMultiPointM;
import com.rgi.geopackage.features.geometry.m.WkbMultiPolygonM;
import com.rgi.geopackage.features.geometry.m.WkbPointM;
import com.rgi.geopackage.features.geometry.m.WkbPolygonM;
import com.rgi.geopackage.features.geometry.xy.WkbGeometryCollection;
import com.rgi.geopackage.features.geometry.xy.WkbLineString;
import com.rgi.geopackage.features.geometry.xy.WkbMultiLineString;
import com.rgi.geopackage.features.geometry.xy.WkbMultiPoint;
import com.rgi.geopackage.features.geometry.xy.WkbMultiPolygon;
import com.rgi.geopackage.features.geometry.xy.WkbPoint;
import com.rgi.geopackage.features.geometry.xy.WkbPolygon;
import com.rgi.geopackage.features.geometry.z.WkbGeometryCollectionZ;
import com.rgi.geopackage.features.geometry.z.WkbGeometryZ;
import com.rgi.geopackage.features.geometry.z.WkbLineStringZ;
import com.rgi.geopackage.features.geometry.z.WkbMultiLineStringZ;
import com.rgi.geopackage.features.geometry.z.WkbMultiPointZ;
import com.rgi.geopackage.features.geometry.z.WkbMultiPolygonZ;
import com.rgi.geopackage.features.geometry.z.WkbPointZ;
import com.rgi.geopackage.features.geometry.z.WkbPolygonZ;
import com.rgi.geopackage.features.geometry.zm.WkbGeometryCollectionZM;
import com.rgi.geopackage.features.geometry.zm.WkbGeometryZM;
import com.rgi.geopackage.features.geometry.zm.WkbLineStringZM;
import com.rgi.geopackage.features.geometry.zm.WkbMultiLineStringZM;
import com.rgi.geopackage.features.geometry.zm.WkbMultiPointZM;
import com.rgi.geopackage.features.geometry.zm.WkbMultiPolygonZM;
import com.rgi.geopackage.features.geometry.zm.WkbPointZM;
import com.rgi.geopackage.features.geometry.zm.WkbPolygonZM;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author Luke Lambert
 */
public class WellKnownBinaryFactory
{
    public WellKnownBinaryFactory()
    {
        this.geometryFactories.put(GeometryType.Geometry          .getCode(), bytes -> { throw new WellKnownBinaryFormatException("Cannot instantiate abstract 'Geometry' type (geometry type code 0)"); } );      // type 0 XY
        this.geometryFactories.put(GeometryType.Point             .getCode(), WkbPoint          ::readWellKnownBinary); // type 1 XY
        this.geometryFactories.put(GeometryType.LineString        .getCode(), WkbLineString     ::readWellKnownBinary); // type 2 XY
        this.geometryFactories.put(GeometryType.Polygon           .getCode(), WkbPolygon        ::readWellKnownBinary); // type 3 XY
        this.geometryFactories.put(GeometryType.MultiPoint        .getCode(), WkbMultiPoint     ::readWellKnownBinary); // type 4 XY
        this.geometryFactories.put(GeometryType.MultiLineString   .getCode(), WkbMultiLineString::readWellKnownBinary); // type 5 XY
        this.geometryFactories.put(GeometryType.MultiPolygon      .getCode(), WkbMultiPolygon   ::readWellKnownBinary); // type 6 XY
        this.geometryFactories.put(GeometryType.GeometryCollection.getCode(), (byteBuffer) -> WkbGeometryCollection.readWellKnownBinary(this::createGeometry, byteBuffer));  // type 7 XY

        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.Geometry          .getCode(), bytes -> { throw new WellKnownBinaryFormatException("Cannot instantiate abstract 'Geometry' type (geometry type code 1000)"); } );   // type 0 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.Point             .getCode(), WkbPointZ          ::readWellKnownBinary);  // type 1 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.LineString        .getCode(), WkbLineStringZ     ::readWellKnownBinary);  // type 2 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.Polygon           .getCode(), WkbPolygonZ        ::readWellKnownBinary);  // type 3 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.MultiPoint        .getCode(), WkbMultiPointZ     ::readWellKnownBinary);  // type 4 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.MultiLineString   .getCode(), WkbMultiLineStringZ::readWellKnownBinary);  // type 5 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.MultiPolygon      .getCode(), WkbMultiPolygonZ   ::readWellKnownBinary);  // type 6 XYZ
        this.geometryFactories.put(WkbGeometryZ.GeometryTypeDimensionalityBase + GeometryType.GeometryCollection.getCode(), (byteBuffer) -> WkbGeometryCollectionZ.readWellKnownBinary(this::createGeometry, byteBuffer));  // type 7 XYZ

        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.Geometry          .getCode(), bytes -> { throw new WellKnownBinaryFormatException("Cannot instantiate abstract 'Geometry' type (geometry type code 2000)"); } );   // type 0 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.Point             .getCode(), WkbPointM          ::readWellKnownBinary);  // type 1 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.LineString        .getCode(), WkbLineStringM     ::readWellKnownBinary);  // type 2 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.Polygon           .getCode(), WkbPolygonM        ::readWellKnownBinary);  // type 3 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.MultiPoint        .getCode(), WkbMultiPointM     ::readWellKnownBinary);  // type 4 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.MultiLineString   .getCode(), WkbMultiLineStringM::readWellKnownBinary);  // type 5 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.MultiPolygon      .getCode(), WkbMultiPolygonM   ::readWellKnownBinary);  // type 6 XYM
        this.geometryFactories.put(WkbGeometryM.GeometryTypeDimensionalityBase + GeometryType.GeometryCollection.getCode(), (byteBuffer) -> WkbGeometryCollectionM.readWellKnownBinary(this::createGeometry, byteBuffer));  // type 7 XYM

        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.Geometry          .getCode(), bytes -> { throw new WellKnownBinaryFormatException("Cannot instantiate abstract 'Geometry' type (geometry type code 3000)"); } );   // type 0 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.Point             .getCode(), WkbPointZM          ::readWellKnownBinary);  // type 1 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.LineString        .getCode(), WkbLineStringZM     ::readWellKnownBinary);  // type 2 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.Polygon           .getCode(), WkbPolygonZM        ::readWellKnownBinary);  // type 3 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.MultiPoint        .getCode(), WkbMultiPointZM     ::readWellKnownBinary);  // type 4 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.MultiLineString   .getCode(), WkbMultiLineStringZM::readWellKnownBinary);  // type 5 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.MultiPolygon      .getCode(), WkbMultiPolygonZM   ::readWellKnownBinary);  // type 6 XYZM
        this.geometryFactories.put(WkbGeometryZM.GeometryTypeDimensionalityBase + GeometryType.GeometryCollection.getCode(), (byteBuffer) -> WkbGeometryCollectionZM.readWellKnownBinary(this::createGeometry, byteBuffer));  // type 7 XYZM
    }

    /**
     * Associate a geometry factory with a specific geometry type code.
     *
     * @param geometryTypeCode
     *             Code representation of the geometry type. Must be in the
     *             range 0 and 2^32 - 1 (range of a 32 bit unsigned integer)
     *             inclusive
     * @param geometryFactory
     *             Callback that creates a geometry that corresponds to the
     *             geometry type code
     */
    public void registerGeometryFactory(final long            geometryTypeCode,
                                        final GeometryFactory geometryFactory)
    {
        if(geometryTypeCode < 0 || geometryTypeCode > maxUnsignedIntValue)
        {
            throw new IllegalArgumentException("Type code must be between 0 and 2^32 - 1 (range of a 32 bit unsigned integer)");
        }

        if(geometryFactory == null)
        {
            throw new IllegalArgumentException("Geometry factory may not be null");
        }

//        if(this.geometryFactories.containsKey(geometryTypeCode))    // TODO do we really want to prohibit this?
//        {
//            throw new IllegalArgumentException("A geometry factory already exists for this geometry type code");
//        }

        this.geometryFactories.put(geometryTypeCode, geometryFactory);
    }

    public Geometry createGeometry(final ByteBuffer wkbByteBuffer) throws WellKnownBinaryFormatException
    {
        try
        {
            if(wkbByteBuffer.limit()-wkbByteBuffer.position() < 5)
            {
                throw new WellKnownBinaryFormatException("Well known binary buffer must contain at least 5 bytes - the first being the byte order indicator, followed by a 4 byte unsigned integer describing the geometry type.");
            }

            // Save the buffer position (.mark()) before we read the well known
            // binary header (this is *not* the GeoPackage binary header). The
            // well known binary header will be re-read by the parsers stored
            // in the geometry factory. This is so the parsers can stand alone,
            // not relying the ByteBuffer to be positioned after the well known
            // binary header.
            wkbByteBuffer.mark();

            final ByteOrder byteOrder = wkbByteBuffer.get() == 0 ? ByteOrder.BIG_ENDIAN
                                                                 : ByteOrder.LITTLE_ENDIAN;

            wkbByteBuffer.order(byteOrder);

            // Read 4 bytes as an /unsigned/ int
            final long geometryType = Integer.toUnsignedLong(wkbByteBuffer.getInt());

            if(!this.geometryFactories.containsKey(geometryType))
            {
                throw new RuntimeException(String.format("Unrecognized geometry type code %d. Recognized geometry types are: %s. Additional types will require a GeoPackage extention to interact with.",
                                                         geometryType,
                                                         this.geometryFactories
                                                             .keySet()
                                                             .stream()
                                                             .map(Object::toString)
                                                             .collect(Collectors.joining(", "))));
            }

            wkbByteBuffer.reset(); // This will reset the position to before the well known binary header.

            return this.geometryFactories
                       .get(geometryType)
                       .create(wkbByteBuffer);
        }
        catch(final BufferUnderflowException bufferUnderflowException)
        {
            throw new WellKnownBinaryFormatException(bufferUnderflowException);
        }
    }

    private final Map geometryFactories = new HashMap<>();

    private static final long maxUnsignedIntValue = 4294967295L; // 2^31 - 1
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy