/** * Copyright 2013 OpenSocial Foundation * Copyright 2013 International Business Machines Corporation * * Licensed 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. * * Utility library for working with Activity Streams Actions * Requires underscorejs. * * @author James M Snell (jasnell@us.ibm.com) */ package com.ibm.common.geojson; import java.io.Serializable; import java.util.Map; import com.google.common.base.Objects; import com.google.common.base.Supplier; import static com.google.common.collect.ImmutableMap.copyOf; import static com.google.common.collect.Maps.newLinkedHashMap; /** * The base class for all GeoJSON objects. The type of object is identified * by the type() property. * @author james * * @param <G> */ @SuppressWarnings("unchecked") public abstract class GeoObject<G extends GeoObject<G>> implements Serializable { private static final long serialVersionUID = 8852811044366213922L; public static enum Type { POINT, MULTIPOINT, LINESTRING, MULTILINESTRING, POLYGON, MULTIPOLYGON, GEOMETRYCOLLECTION, FEATURE, FEATURECOLLECTION } public static abstract class Builder <G extends GeoObject<G>, B extends Builder<G,B>> implements Supplier<G> { protected boolean withBoundingBox = false; protected Type type; protected Map<String,Object> data = newLinkedHashMap(); /** * Auto-calculate the bounding box when the object is created * @return Builder */ public B calculateBoundingBox() { this.withBoundingBox = true; return (B)this; } /** * Use the given object as a template when creating this one * @param geo GeObject<?> * @return Builder */ protected B from(GeoObject<?> geo) { data.putAll(geo.data); return (B)this; } /** * Set the object type * @param type Type * @return Builder */ public B type(Type type) { this.type = type; return (B)this; } /** * Set the CRS * @param crs CRS * @return Builder */ public B crs(CRS crs) { return set("crs", crs); } /** * Set the bounding box explicitly * @param bbox BoundingBox * @return Builder * @see GeoObject.Builder.calculateBoundingBox() */ public B boundingBox(BoundingBox bbox) { return set("bbox", bbox); } /** * Set an additional property on this object * @param name String * @param val Object * @return Builder */ public B set(String name, Object val) { if (val != null) this.data.put(name,val); else if (this.data.containsKey(name)) this.data.remove(name); return (B)this; } /** * Get the built object */ public final G get() { preGet(); G g = doGet(); return withBoundingBox ? g.withBoundingBox() : g; } protected void preGet() {} protected abstract G doGet(); } final Type type; final Map<String,Object> data; protected GeoObject(Builder<?,?> builder) { this.type = builder.type; this.data = copyOf(builder.data); } /** * Return the type of object * @return Type */ public Type type() { return type; } public <T>T get(String name) { return (T)data.get(name); } public <T>T get(String name, T defaultValue) { T val = get(name); return val != null ? val : defaultValue; } public boolean has(String name) { return data.containsKey(name); } /** * Return the CRS for this object * @return CRS */ public CRS crs() { return this.<CRS>get("crs", null); } /** * Return the bounding box for this object * @return BoundingBox */ public BoundingBox boundingBox() { return this.<BoundingBox>get("bbox", null); } /** * Return a copy of this object with a calculated bounding box * @return G (a copy of this object) */ public final G withBoundingBox() { return has("bbox") ? (G)this : makeWithBoundingBox(); } protected abstract G makeWithBoundingBox(); public static final Position position(float x, float y) { return new Position.Builder() .northing(x) .easting(y) .get(); } public static final Position position(float x, float y, float z) { return new Position.Builder() .northing(x) .easting(y) .altitude(z) .get(); } public String toString() { return Objects.toStringHelper(GeoObject.class) .add("type", type) .add("data", data) .toString(); } protected static abstract class AbstractSerializedForm <G extends GeoObject<G>, B extends GeoObject.Builder<G,B>> implements Serializable { private static final long serialVersionUID = -1950126276150975248L; private Type type; private Map<String,Object> data; AbstractSerializedForm(G obj) { this.type = obj.type(); this.data = obj.data; } protected Object doReadResolve() { B builder = builder(); builder.type(type); for (Map.Entry<String,Object> entry : data.entrySet()) { String key = entry.getKey(); Object val = entry.getValue(); if (!handle(builder, key,val)) builder.data.put(key,val); } return builder.get(); } protected boolean handle( B builder, String key, Object val) { return false; } protected abstract B builder(); } }