edu.umn.cs.spatialHadoop.core.OGCESRIShape.java Source code

Java tutorial

Introduction

Here is the source code for edu.umn.cs.spatialHadoop.core.OGCESRIShape.java

Source

/***********************************************************************
* Copyright (c) 2015 by Regents of the University of Minnesota.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License, Version 2.0 which 
* accompanies this distribution and is available at
* http://www.opensource.org/licenses/apache2.0.php.
*
*************************************************************************/
package edu.umn.cs.spatialHadoop.core;

import java.awt.Color;
import java.awt.Graphics;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.io.Text;

import com.esri.core.geometry.MultiPath;
import com.esri.core.geometry.Polygon;
import com.esri.core.geometry.Polyline;
import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection;
import com.esri.core.geometry.ogc.OGCGeometry;
import com.esri.core.geometry.ogc.OGCGeometryCollection;
import com.esri.core.geometry.ogc.OGCLineString;
import com.esri.core.geometry.ogc.OGCPoint;

import edu.umn.cs.spatialHadoop.io.TextSerializerHelper;

/**
 * A shape class that represents an OGC compliant geometry. The geometry is
 * enclosed inside the class and all calls are delegated to it. The class also
 * holds extra information for each records that could represent other columns
 * for each records. The text representation is assumed to be some kind of CSV.
 * The shape is always the first column in that CSV. The text representation of
 * the shape could be either a WTK (Well Known Text) or a binary representation.
 * The WKT can be generated with PostGIS using the function ST_AsText(geom). An
 * example may look like this:<br>
 * <code>
 * POLYGON ((-89 43,-89 50,-97 50,-97 43,-89 43))
 * </code> The binary representation can be generated from PostGIS by selecting
 * the geom column using a normal select statement. When a shape is parsed, we
 * detect the format and use the appropriate parser. When writing to text, we
 * always use the binary representation as it is faster and more compact. For
 * binary serialization/deserialization, we use the PostGIS writer and parser.
 * 
 * @author Ahmed Eldawy
 * 
 */
public class OGCESRIShape implements Shape {

    private static final Log LOG = LogFactory.getLog(OGCESRIShape.class);

    /**
     * The underlying geometry
     */
    public OGCGeometry geom;

    public OGCESRIShape() {
        this(null);
    }

    public OGCESRIShape(OGCGeometry geom) {
        this.geom = geom;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        byte[] bytes = geom.asBinary().array();
        out.writeInt(bytes.length);
        out.write(bytes);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        int size = in.readInt();
        byte[] bytes = new byte[size];
        in.readFully(bytes);
        geom = OGCGeometry.fromBinary(ByteBuffer.wrap(bytes));
    }

    /**
     * Convert a string containing a hex string to a byte array of binary.
     * For example, the string "AABB" is converted to the byte array {0xAA, 0XBB}
     * @param hex
     * @return
     */
    public static byte[] hexToBytes(String hex) {
        byte[] bytes = new byte[(hex.length() + 1) / 2];
        for (int i = 0; i < hex.length(); i++) {
            byte x = (byte) hex.charAt(i);
            if (x >= '0' && x <= '9')
                x -= '0';
            else if (x >= 'a' && x <= 'f')
                x = (byte) ((x - 'a') + 0xa);
            else if (x >= 'A' && x <= 'F')
                x = (byte) ((x - 'A') + 0xA);
            else
                throw new RuntimeException("Invalid hex char " + x);
            if (i % 2 == 0)
                x <<= 4;
            bytes[i / 2] |= x;
        }
        return bytes;
    }

    @Override
    public Text toText(Text text) {
        TextSerializerHelper.serializeGeometry(text, geom, '\0');
        return text;
    }

    @Override
    public void fromText(Text text) {
        try {
            geom = TextSerializerHelper.consumeGeometryESRI(text, '\0');
        } catch (RuntimeException e) {
            LOG.error("Error parsing: " + text);
            throw e;
        }
    }

    @Override
    public Rectangle getMBR() {
        if (geom.isEmpty())
            return null;
        com.esri.core.geometry.Polygon mbr = (com.esri.core.geometry.Polygon) geom.envelope().getEsriGeometry();
        int pointCount = mbr.getPointCount();
        double xmin = mbr.getPoint(0).getX();
        double ymin = mbr.getPoint(0).getY();
        double xmax = xmin, ymax = ymin;
        for (int i = 1; i < pointCount; i++) {
            com.esri.core.geometry.Point point = mbr.getPoint(i);
            if (point.getX() < xmin)
                xmin = point.getX();
            if (point.getX() > xmax)
                xmax = point.getX();
            if (point.getY() < ymin)
                ymin = point.getY();
            if (point.getY() > ymax)
                ymax = point.getY();
        }

        return new Rectangle(xmin, ymin, xmax, ymax);
    }

    @Override
    public double distanceTo(double x, double y) {
        OGCPoint point = new OGCPoint(new com.esri.core.geometry.Point(x, y), this.geom.getEsriSpatialReference());
        return this.geom.distance(point);
    }

    @Override
    public boolean isIntersected(Shape s) {
        Rectangle s_mbr = s.getMBR();
        Rectangle this_mbr = this.getMBR();
        if (s_mbr == null || this_mbr == null)
            return false;
        // Filter step
        if (!s_mbr.isIntersected(this_mbr))
            return false;
        try {
            if (s instanceof OGCESRIShape)
                return geom.intersects(((OGCESRIShape) s).geom);
        } catch (NullPointerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        ArrayList<OGCGeometry> points = new ArrayList<OGCGeometry>();
        points.add(
                new OGCPoint(new com.esri.core.geometry.Point(s_mbr.x1, s_mbr.y1), geom.getEsriSpatialReference()));
        points.add(
                new OGCPoint(new com.esri.core.geometry.Point(s_mbr.x2, s_mbr.y2), geom.getEsriSpatialReference()));

        OGCGeometryCollection all_points = new OGCConcreteGeometryCollection(points,
                geom.getEsriSpatialReference());

        return geom.intersects(all_points.envelope());
    }

    @Override
    public Shape clone() {
        OGCESRIShape copy = new OGCESRIShape(this.geom);
        return copy;
    }

    @Override
    public String toString() {
        return geom.asText();
    }

    @Override
    public void draw(Graphics g, Rectangle fileMBR, int imageWidth, int imageHeight, double scale) {
        OGCGeometry geom = this.geom;
        Color shape_color = g.getColor();
        if (geom instanceof OGCGeometryCollection) {
            OGCGeometryCollection geom_coll = (OGCGeometryCollection) geom;
            for (int i = 0; i < geom_coll.numGeometries(); i++) {
                OGCGeometry sub_geom = geom_coll.geometryN(i);
                // Recursive call to draw each geometry
                new OGCESRIShape(sub_geom).draw(g, fileMBR, imageWidth, imageHeight, scale);
            }
        } else if (geom.getEsriGeometry() instanceof MultiPath) {
            MultiPath path = (MultiPath) geom.getEsriGeometry();
            double sub_geom_alpha = path.calculateLength2D() * scale;
            int color_alpha = sub_geom_alpha > 1.0 ? 255 : (int) Math.round(sub_geom_alpha * 255);

            if (color_alpha == 0)
                return;

            int[] xpoints = new int[path.getPointCount()];
            int[] ypoints = new int[path.getPointCount()];

            for (int i = 0; i < path.getPointCount(); i++) {
                double px = path.getPoint(i).getX();
                double py = path.getPoint(i).getY();

                // Transform a point in the polygon to image coordinates
                xpoints[i] = (int) Math.round((px - fileMBR.x1) * imageWidth / fileMBR.getWidth());
                ypoints[i] = (int) Math.round((py - fileMBR.y1) * imageHeight / fileMBR.getHeight());
            }

            // Draw the polygon
            g.setColor(new Color((shape_color.getRGB() & 0x00FFFFFF) | (color_alpha << 24), true));
            if (path instanceof Polygon)
                g.drawPolygon(xpoints, ypoints, path.getPointCount());
            else if (path instanceof Polyline)
                g.drawPolyline(xpoints, ypoints, path.getPointCount());
        }
    }

    @Override
    public void draw(Graphics g, double xscale, double yscale) {
        drawESRIGeom(g, geom, xscale, yscale);
    }

    public void drawESRIGeom(Graphics g, OGCGeometry geom, double xscale, double yscale) {
        if (geom instanceof OGCLineString) {
            OGCLineString linestring = (OGCLineString) geom;
            int[] xs = new int[linestring.numPoints()];
            int[] ys = new int[linestring.numPoints()];
            int n = 0;
            for (int i = 0; i < n; i++) {
                OGCPoint point = linestring.pointN(i);
                xs[n] = (int) Math.round(point.X() * xscale);
                ys[n] = (int) Math.round(point.Y() * yscale);
                // Increment number of point if this point is different than previous ones
                if (n == 0 || xs[n] != xs[n - 1] || ys[n] != ys[n - 1])
                    n++;
            }
            g.drawPolyline(xs, ys, n);
        } else {
            throw new RuntimeException("Cannot draw a shape of type " + geom.getClass());
        }
    }
}