/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.locationtech.spatial4j.io;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeFactory;
import com.vividsolutions.jts.geom.LinearRing;
/**
* @see PolyshapeWriter
*/
public class PolyshapeReader implements ShapeReader {
final SpatialContext ctx;
final ShapeFactory shpFactory;
public PolyshapeReader(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
this.shpFactory = ctx.getShapeFactory();
}
@Override
public String getFormatName() {
return ShapeIO.POLY;
}
@Override
public Shape read(Object value) throws IOException, ParseException, InvalidShapeException {
return read(new StringReader(value.toString().trim()));
}
@Override
public Shape readIfSupported(Object value) throws InvalidShapeException {
String v = value.toString().trim();
char first = v.charAt(0);
if(first >= '0' && first <= '9') {
try {
return read(new StringReader(v));
} catch (ParseException e) {
} catch (IOException e) {
}
}
return null;
}
// --------------------------------------------------------------
// Read GeoJSON
// --------------------------------------------------------------
@Override
public final Shape read(Reader r) throws ParseException, IOException
{
XReader reader = new XReader(r, shpFactory);
Double arg = null;
Shape lastShape = null;
List<Shape> shapes = null;
while(!reader.isDone()) {
char event = reader.readKey();
if(event<'0' || event > '9') {
if(event == PolyshapeWriter.KEY_SEPERATOR) {
continue; // read the next key
}
throw new ParseException("expecting a shape key. not '"+event+"'", -1);
}
if(lastShape!=null) {
if(shapes==null) {
shapes = new ArrayList<Shape>();
}
shapes.add(lastShape);
}
arg = null;
if(reader.peek()==PolyshapeWriter.KEY_ARG_START) {
reader.readKey(); // skip the key
arg = reader.readDouble();
if(reader.readKey()!=PolyshapeWriter.KEY_ARG_END) {
throw new ParseException("expecting an argument end", -1);
}
}
if(reader.isEvent()) {
throw new ParseException("Invalid input. Event should be followed by data", -1);
}
switch(event) {
case PolyshapeWriter.KEY_POINT: {
lastShape = shpFactory.pointXY(shpFactory.normX(reader.readLat()), shpFactory.normY(reader.readLng()));
break;
}
case PolyshapeWriter.KEY_LINE: {
ShapeFactory.LineStringBuilder lineBuilder = shpFactory.lineString();
reader.readPoints(lineBuilder);
if(arg!=null) {
lineBuilder.buffer(shpFactory.normDist(arg));
}
lastShape = lineBuilder.build();
break;
}
case PolyshapeWriter.KEY_BOX: {
double lat1 = shpFactory.normX(reader.readLat());
double lon1 = shpFactory.normY(reader.readLng());
lastShape = shpFactory.rect(lat1, shpFactory.normX(reader.readLat()),
lon1, shpFactory.normY(reader.readLng()));
break;
}
case PolyshapeWriter.KEY_MULTIPOINT : {
lastShape = reader.readPoints(shpFactory.multiPoint()).build();
break;
}
case PolyshapeWriter.KEY_CIRCLE : {
if(arg==null) {
throw new IllegalArgumentException("the input should have a radius argument");
}
lastShape = shpFactory.circle(shpFactory.normX(reader.readLat()), shpFactory.normY(reader.readLng()),
shpFactory.normDist(arg.doubleValue()));
break;
}
case PolyshapeWriter.KEY_POLYGON: {
lastShape = readPolygon(reader);
break;
}
default: {
throw new ParseException("unhandled key: "+event, -1);
}
}
}
if(shapes!=null) {
if(lastShape!=null) {
shapes.add(lastShape);
}
ShapeFactory.MultiShapeBuilder<Shape> multiBuilder = shpFactory.multiShape(Shape.class);
for (Shape shp : shapes) {
multiBuilder.add(shp);
}
return multiBuilder.build();
}
return lastShape;
}
protected Shape readPolygon(XReader reader) throws IOException {
ShapeFactory.PolygonBuilder polyBuilder = shpFactory.polygon();
reader.readPoints(polyBuilder);
if(!reader.isDone() && reader.peek()==PolyshapeWriter.KEY_ARG_START) {
List<LinearRing> list = new ArrayList<LinearRing>();
while(reader.isEvent() && reader.peek()==PolyshapeWriter.KEY_ARG_START) {
reader.readKey(); // eat the event;
reader.readPoints(polyBuilder.hole()).endHole();
}
}
return polyBuilder.build();
}
/**
* from Apache 2.0 licensed:
* https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/PolyUtil.java
*/
public static class XReader {
int lat = 0;
int lng = 0;
int head = -1;
final Reader input;
final ShapeFactory shpFactory;
public XReader(final Reader input, ShapeFactory shpFactory) throws IOException {
this.input = input;
this.shpFactory = shpFactory;
head = input.read();
}
public <T extends ShapeFactory.PointsBuilder> T readPoints(T builder) throws IOException {
while(isData()) {
builder.pointXY(shpFactory.normX(readLat()), shpFactory.normY(readLng()));
}
return builder;
}
public double readLat() throws IOException {
lat += readInt();
return lat * 1e-5;
}
public double readLng() throws IOException {
lng += readInt();
return lng * 1e-5;
}
public double readDouble() throws IOException {
return readInt() * 1e-5;
}
public int peek() {
return head;
}
public char readKey() throws IOException {
lat = lng = 0; // reset the offset
char key = (char)head;
head = input.read();
return key;
}
public boolean isData() {
return head >= '?';
}
public boolean isDone() {
return head < 0;
}
public boolean isEvent() {
return head > 0 && head < '?';
}
int readInt() throws IOException
{
int b;
int result = 1;
int shift = 0;
do {
b = head - 63 - 1;
result += b << shift;
shift += 5;
head = input.read();
} while (b >= 0x1f);
return (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
}
}
}