LUCENE-7936: Add Geo3d framework for serialization and deserialization.

This commit is contained in:
Karl Wright 2017-08-23 10:00:31 -04:00
parent 19a8be3c9f
commit 6b8f98db93
5 changed files with 248 additions and 4 deletions

View File

@ -16,13 +16,16 @@
*/
package org.apache.lucene.spatial3d.geom;
import java.io.OutputStream;
import java.io.IOException;
/**
* All Geo3D shapes can derive from this base class, which furnishes
* some common code
*
* @lucene.internal
*/
public abstract class BasePlanetObject implements PlanetObject {
public abstract class BasePlanetObject implements PlanetObject, SerializableObject {
/** This is the planet model embedded in all objects derived from this
* class. */
@ -40,6 +43,11 @@ public abstract class BasePlanetObject implements PlanetObject {
return planetModel;
}
@Override
public void write(final OutputStream outputStream) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
return planetModel.hashCode();
@ -51,6 +59,8 @@ public abstract class BasePlanetObject implements PlanetObject {
return false;
return planetModel.equals(((BasePlanetObject)o).planetModel);
}
}

View File

@ -15,7 +15,10 @@
* limitations under the License.
*/
package org.apache.lucene.spatial3d.geom;
import java.io.InputStream;
import java.io.IOException;
/**
* This class represents a degenerate point bounding box.
* It is not a simple GeoPoint because we must have the latitude and longitude.
@ -39,6 +42,16 @@ class GeoDegeneratePoint extends GeoPoint implements GeoBBox, GeoCircle {
this.edgePoints = new GeoPoint[]{this};
}
/** Constructor for deserialization.
*@param planetModel is the planet model to use.
*@param inputStream is the input stream.
*/
public GeoDegeneratePoint(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
super(planetModel, inputStream);
this.planetModel = planetModel;
this.edgePoints = new GeoPoint[]{this};
}
@Override
public PlanetModel getPlanetModel() {
return planetModel;

View File

@ -15,13 +15,17 @@
* limitations under the License.
*/
package org.apache.lucene.spatial3d.geom;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
/**
* This class represents a point on the surface of a sphere or ellipsoid.
*
* @lucene.experimental
*/
public class GeoPoint extends Vector {
public class GeoPoint extends Vector implements SerializableObject {
// By making lazily-evaluated variables be "volatile", we guarantee atomicity when they
// are updated. This is necessary if we are using these classes in a multi-thread fashion,
@ -73,6 +77,14 @@ public class GeoPoint extends Vector {
this(planetModel, Math.sin(lat), Math.sin(lon), Math.cos(lat), Math.cos(lon), lat, lon);
}
/** Construct a GeoPoint from a planet model and an input stream.
*/
public GeoPoint(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
// Note: this relies on left-right parameter execution order!! Much code depends on that though and
// it is apparently in a java spec: https://stackoverflow.com/questions/2201688/order-of-execution-of-parameters-guarantees-in-java
this(planetModel, SerializableObject.readDouble(inputStream), SerializableObject.readDouble(inputStream));
}
/** Construct a GeoPoint from a unit (x,y,z) vector and a magnitude.
* @param magnitude is the desired magnitude, provided to put the point on the ellipsoid.
* @param x is the unit x value.
@ -115,6 +127,12 @@ public class GeoPoint extends Vector {
super(x, y, z);
}
@Override
public void write(final OutputStream outputStream) throws IOException {
SerializableObject.writeDouble(outputStream, getLatitude());
SerializableObject.writeDouble(outputStream, getLongitude());
}
/** Compute an arc distance between two points.
* Note: this is an angular distance, and not a surface distance, and is therefore independent of planet model.
* For surface distance, see {@link PlanetModel#surfaceDistance(GeoPoint, GeoPoint)}
@ -190,4 +208,6 @@ public class GeoPoint extends Vector {
}
return "[lat="+getLatitude()+", lon="+getLongitude()+"("+super.toString()+")]";
}
}

View File

@ -26,4 +26,5 @@ public interface PlanetObject {
/** Returns the {@link PlanetModel} provided when this shape was created. */
PlanetModel getPlanetModel();
}

View File

@ -0,0 +1,200 @@
/*
* 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.apache.lucene.spatial3d.geom;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Indicates that a geo3d object can be serialized and deserialized.
*
* @lucene.experimental
*/
public interface SerializableObject {
/** Serialize to output stream.
* @param outputStream is the output stream to write to.
*/
void write(OutputStream outputStream) throws IOException;
/** Write an object to a stream.
* @param outputStream is the output stream.
* @param object is the object to write.
*/
static void writeObject(final OutputStream outputStream, final SerializableObject object) throws IOException {
writeString(outputStream, object.getClass().getName());
object.write(outputStream);
}
/** Read an object from a stream (for objects that need a PlanetModel).
* @param planetModel is the planet model to use to deserialize the object.
* @param inputStream is the input stream.
* @return the deserialized object.
*/
static SerializableObject readObject(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
// Read the class name
final String className = readString(inputStream);
try {
// Look for the class
final Class<?> clazz = Class.forName(className);
// Look for the right constructor
final Constructor<?> c = clazz.getDeclaredConstructor(PlanetModel.class, InputStream.class);
// Invoke it
final Object object = c.newInstance(planetModel, inputStream);
// check whether caste will work
if (!(object instanceof SerializableObject)) {
throw new IOException("Object "+className+" does not implement SerializableObject");
}
return (SerializableObject)object;
} catch (ClassNotFoundException e) {
throw new IOException("Can't find class "+className+" for deserialization: "+e.getMessage(), e);
} catch (InstantiationException e) {
throw new IOException("Instantiation exception for class "+className+": "+e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new IOException("Illegal access creating class "+className+": "+e.getMessage(), e);
} catch (NoSuchMethodException e) {
throw new IOException("No such method exception for class "+className+": "+e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new IOException("Exception instantiating class "+className+": "+e.getMessage(), e);
}
}
/** Read an object from a stream (for objects that do not need a PlanetModel).
* @param inputStream is the input stream.
* @return the deserialized object.
*/
static SerializableObject readObject(final InputStream inputStream) throws IOException {
// Read the class name
final String className = readString(inputStream);
try {
// Look for the class
final Class<?> clazz = Class.forName(className);
// Look for the right constructor
final Constructor<?> c = clazz.getDeclaredConstructor(InputStream.class);
// Invoke it
final Object object = c.newInstance(inputStream);
// check whether caste will work
if (!(object instanceof SerializableObject)) {
throw new IOException("Object "+className+" does not implement SerializableObject");
}
return (SerializableObject)object;
} catch (ClassNotFoundException e) {
throw new IOException("Can't find class "+className+" for deserialization: "+e.getMessage(), e);
} catch (InstantiationException e) {
throw new IOException("Instantiation exception for class "+className+": "+e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new IOException("Illegal access creating class "+className+": "+e.getMessage(), e);
} catch (NoSuchMethodException e) {
throw new IOException("No such method exception for class "+className+": "+e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new IOException("Exception instantiating class "+className+": "+e.getMessage(), e);
}
}
/** Write a string to a stream.
* @param outputStream is the output stream.
* @param value is the string to write.
*/
static void writeString(final OutputStream outputStream, final String value) throws IOException {
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
writeInt(outputStream, bytes.length);
outputStream.write(bytes);
}
/** Read a string from a stream.
* @param inputStream is the stream to read from.
* @return the string that was read.
*/
static String readString(final InputStream inputStream) throws IOException {
int stringLength = readInt(inputStream);
int stringOffset = 0;
final byte[] bytes = new byte[stringLength];
while (stringLength > 0) {
final int amt = inputStream.read(bytes, stringOffset, stringLength);
if (amt == -1) {
throw new IOException("Unexpected end of input stream");
}
stringOffset += amt;
stringLength -= amt;
}
return new String(bytes, StandardCharsets.UTF_8);
}
/** Write a double to a stream.
* @param outputStream is the output stream.
* @param value is the value to write.
*/
static void writeDouble(final OutputStream outputStream, final double value) throws IOException {
writeLong(outputStream, Double.doubleToLongBits(value));
}
/** Read a double from a stream.
* @param inputStream is the input stream.
* @return the double value read from the stream.
*/
static double readDouble(final InputStream inputStream) throws IOException {
return Double.longBitsToDouble(readLong(inputStream));
}
/** Write a long to a stream.
* @param outputStream is the output stream.
* @param value is the value to write.
*/
static void writeLong(final OutputStream outputStream, final long value) throws IOException {
writeInt(outputStream, (int)value);
writeInt(outputStream, (int)(value >> 32));
}
/** Read a long from a stream.
* @param inputStream is the input stream.
* @return the long value read from the stream.
*/
static long readLong(final InputStream inputStream) throws IOException {
final long lower = ((long)(readInt(inputStream))) & 0x00000000ffffffffL;
final long upper = (((long)(readInt(inputStream))) << 32) & 0xffffffff00000000L;
return lower + upper;
}
/** Write an int to a stream.
* @param outputStream is the output stream.
* @param value is the value to write.
*/
static void writeInt(final OutputStream outputStream, final int value) throws IOException {
outputStream.write(value);
outputStream.write(value >> 8);
outputStream.write(value >> 16);
outputStream.write(value >> 24);
}
/** Read an int from a stream.
* @param inputStream is the input stream.
* @return the value read from the stream.
*/
static int readInt(final InputStream inputStream) throws IOException {
final int l1 = (inputStream.read()) & 0x000000ff;
final int l2 = (inputStream.read() << 8) & 0x0000ff00;
final int l3 = (inputStream.read() << 16) & 0x00ff0000;
final int l4 = (inputStream.read() << 24) & 0xff000000;
return l1 + l2 + l3 + l4;
}
}