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

Java tutorial

Introduction

Here is the source code for edu.umn.cs.spatialHadoop.core.OGCJTSShape.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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.io.Text;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import com.vividsolutions.jts.io.WKTReader;

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 OGCJTSShape implements Shape {

    @SuppressWarnings("unused")
    private static final Log LOG = LogFactory.getLog(OGCJTSShape.class);

    private final WKTReader wktReader = new WKTReader();
    private final WKBWriter wkbWriter = new WKBWriter();
    private final WKBReader wkbReader = new WKBReader();

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

    public OGCJTSShape() {
        this(null);
    }

    public OGCJTSShape(Geometry geom) {
        this.geom = geom;
    }

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

    @Override
    public void readFields(DataInput in) throws IOException {
        try {
            byte[] wkb = new byte[in.readInt()];
            in.readFully(wkb);
            geom = wkbReader.read(wkb);
        } catch (ParseException e) {
            e.printStackTrace();
            throw new IOException(e);
        }
    }

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

    public Geometry parseText(String str) throws ParseException {
        Geometry geom = null;
        try {
            // Parse string as well known text (WKT)
            geom = wktReader.read(str);
        } catch (ParseException e) {
            try {
                // Error parsing from WKT, try hex string instead
                byte[] binary = WKBReader.hexToBytes(str);
                geom = wkbReader.read(binary);
            } catch (RuntimeException e1) {
                // Cannot parse text. Just return null
            }
        }
        return geom;
    }

    @Override
    public void fromText(Text text) {
        this.geom = TextSerializerHelper.consumeGeometryJTS(text, '\0');
    }

    @Override
    public Rectangle getMBR() {
        if (geom == null)
            return null;
        Coordinate[] coords = geom.getEnvelope().getCoordinates();
        if (coords.length == 0)
            return null;
        double xmin, ymin, xmax, ymax;
        if (coords.length == 1) {
            // A point
            xmin = coords[0].x;
            ymin = coords[0].y;
            xmax = xmin + Math.ulp(xmin);
            ymax = ymin + Math.ulp(ymin);
        } else if (coords.length == 2) {
            // An orthogonal line
            if (coords[0].x == coords[1].x) {
                // A vertical line
                xmin = coords[0].x;
                xmax = xmin + Math.ulp(xmin);
                ymin = Math.min(coords[0].y, coords[1].y);
                ymax = Math.max(coords[0].y, coords[1].y);
            } else {
                // A horizontal line
                xmin = Math.min(coords[0].x, coords[1].x);
                xmax = Math.max(coords[0].x, coords[1].x);
                ymin = coords[0].y;
                ymax = ymin + Math.ulp(ymin);
            }
        } else if (coords.length == 4 || coords.length == 5) {
            // A rectangle
            xmin = Math.min(coords[0].x, coords[2].x);
            ymin = Math.min(coords[0].y, coords[2].y);
            xmax = Math.max(coords[0].x, coords[2].x);
            ymax = Math.max(coords[0].y, coords[2].y);
        } else {
            throw new RuntimeException("Cannot get MBR of " + geom);
        }

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

    @Override
    public double distanceTo(double x, double y) {
        return this.geom.distance(geom.getFactory().createPoint(new Coordinate(x, y)));
    }

    @Override
    public boolean isIntersected(Shape s) {
        if (this.geom == null)
            return false;
        if (s instanceof OGCJTSShape) {
            return geom.intersects(((OGCJTSShape) s).geom);
        }
        Rectangle mbr = s.getMBR();
        Coordinate[] coordinates = new Coordinate[5];
        coordinates[0] = new Coordinate(mbr.x1, mbr.y1);
        coordinates[1] = new Coordinate(mbr.x1, mbr.y2);
        coordinates[2] = new Coordinate(mbr.x2, mbr.y2);
        coordinates[3] = new Coordinate(mbr.x2, mbr.y1);
        coordinates[4] = coordinates[0];
        Polygon mbrPoly = geom.getFactory().createPolygon(geom.getFactory().createLinearRing(coordinates), null);

        return geom.intersects(mbrPoly);
    }

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

    @Override
    public String toString() {
        return geom == null ? "(empty)" : geom.toString();
    }

    @Override
    public void draw(Graphics g, Rectangle fileMBR, int imageWidth, int imageHeight, double scale) {
        Geometry geom = this.geom;
        Color shape_color = g.getColor();

        drawJTSShape(g, geom, fileMBR, imageWidth, imageHeight, scale, shape_color);
    }

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

    /**
     * Draw the given JTS Geometry to the graphics using specific scales in x and y
     * @param g Graphics to draw to
     * @param geom The geometry to draw
     * @param xscale The scale of the x-axis in terms in pixels/units
     * @param yscale The scale of the y-axis in terms of pixels/units
     * @param fill Whether to fill the shape or just draw an outline
     */
    public static void drawJTSGeom(Graphics g, Geometry geom, double xscale, double yscale, boolean fill) {
        if (geom instanceof GeometryCollection) {
            GeometryCollection geom_coll = (GeometryCollection) geom;
            for (int i = 0; i < geom_coll.getNumGeometries(); i++) {
                Geometry sub_geom = geom_coll.getGeometryN(i);
                // Recursive call to draw each geometry
                drawJTSGeom(g, sub_geom, xscale, yscale, fill);
            }
        } else if (geom instanceof com.vividsolutions.jts.geom.Polygon) {
            com.vividsolutions.jts.geom.Polygon poly = (com.vividsolutions.jts.geom.Polygon) geom;

            for (int i = 0; i < poly.getNumInteriorRing(); i++) {
                LineString ring = poly.getInteriorRingN(i);
                drawJTSGeom(g, ring, xscale, yscale, fill);
            }

            drawJTSGeom(g, poly.getExteriorRing(), xscale, yscale, fill);
        } else if (geom instanceof LineString) {
            LineString line = (LineString) geom;
            double geom_alpha = line.getLength() * (xscale + yscale) / 2.0;
            int color_alpha = geom_alpha > 1.0 ? 255 : (int) Math.round(geom_alpha * 255);
            if (color_alpha == 0)
                return;

            int[] xpoints = new int[line.getNumPoints()];
            int[] ypoints = new int[line.getNumPoints()];
            int n = 0;

            for (int i = 0; i < xpoints.length; i++) {
                double px = line.getPointN(i).getX();
                double py = line.getPointN(i).getY();

                // Transform a point in the polygon to image coordinates
                xpoints[n] = (int) Math.round(px * xscale);
                ypoints[n] = (int) Math.round(py * yscale);
                // Include this point only if first point or different than previous point
                if (n == 0 || xpoints[n] != xpoints[n - 1] || ypoints[n] != ypoints[n - 1])
                    n++;
            }

            // Draw the polygon
            //graphics.setColor(new Color((shape_color.getRGB() & 0x00FFFFFF) | (color_alpha << 24), true));
            if (n == 1)
                g.fillRect(xpoints[0], ypoints[0], 1, 1);
            else if (!fill)
                g.drawPolyline(xpoints, ypoints, n);
            else
                g.fillPolygon(xpoints, ypoints, n);
        }
    }

    /**
     * Plots a Geometry from the library JTS into the given image.
     * @param graphics
     * @param geom
     * @param fileMbr
     * @param imageWidth
     * @param imageHeight
     * @param scale
     * @param shape_color
     * @deprecated - use {@link #drawJTSGeom(Graphics, Geometry, double, double)}
     */
    @Deprecated
    private static void drawJTSShape(Graphics graphics, Geometry geom, Rectangle fileMbr, int imageWidth,
            int imageHeight, double scale, Color shape_color) {
        if (geom instanceof GeometryCollection) {
            GeometryCollection geom_coll = (GeometryCollection) geom;
            for (int i = 0; i < geom_coll.getNumGeometries(); i++) {
                Geometry sub_geom = geom_coll.getGeometryN(i);
                // Recursive call to draw each geometry
                drawJTSShape(graphics, sub_geom, fileMbr, imageWidth, imageHeight, scale, shape_color);
            }
        } else if (geom instanceof com.vividsolutions.jts.geom.Polygon) {
            com.vividsolutions.jts.geom.Polygon poly = (com.vividsolutions.jts.geom.Polygon) geom;

            for (int i = 0; i < poly.getNumInteriorRing(); i++) {
                LineString ring = poly.getInteriorRingN(i);
                drawJTSShape(graphics, ring, fileMbr, imageWidth, imageHeight, scale, shape_color);
            }

            drawJTSShape(graphics, poly.getExteriorRing(), fileMbr, imageWidth, imageHeight, scale, shape_color);
        } else if (geom instanceof LineString) {
            LineString line = (LineString) geom;
            double geom_alpha = line.getLength() * scale;
            int color_alpha = geom_alpha > 1.0 ? 255 : (int) Math.round(geom_alpha * 255);
            if (color_alpha == 0)
                return;

            int[] xpoints = new int[line.getNumPoints()];
            int[] ypoints = new int[line.getNumPoints()];

            for (int i = 0; i < xpoints.length; i++) {
                double px = line.getPointN(i).getX();
                double py = line.getPointN(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
            //graphics.setColor(new Color((shape_color.getRGB() & 0x00FFFFFF) | (color_alpha << 24), true));
            graphics.drawPolyline(xpoints, ypoints, xpoints.length);
        }
    }

}