/* Copyright 2013 The jeo project. All rights reserved. * * 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. */ package io.jeo.vector; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import io.jeo.geom.Geom; import io.jeo.geom.Geom.Type; import io.jeo.proj.Proj; import org.osgeo.proj4j.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Geometry; /** * Builder for {@link Schema} objects. * <p> * Example usage: * <pre> * Schema schema = new SchemaBuilder("cities").field("loc", Point.class, "epsg:4326") * .field("name", String.class).field("pop", Integer.class).schema(); * </pre> * </p> * * @author Justin Deoliveira, OpenGeo */ public class SchemaBuilder { String name; String uri; CoordinateReferenceSystem crs; List<Field> fields = new ArrayList<Field>(); Map<String,Object> props; /** * Creates a new builder object. * * @param name The name of the schema. */ public SchemaBuilder(String name) { this.name = name; } /** * Sets the namespace of the schema. * * @param uri A namespace uri. * * @return This builder. */ public SchemaBuilder uri(String uri) { this.uri = uri; return this; } /** * Sets the projection of the schema. * * @param crs The projection. * * @return This builder. */ public SchemaBuilder crs(CoordinateReferenceSystem crs) { this.crs = crs; return this; } /** * Sets the projection of the schema. * * @param srs The projection specifier. * * @return This builder. */ public SchemaBuilder crs(String srs) { this.crs = Proj.crs(srs); return this; } /** * Adds a field to the schema being built. * * @param name The field name. * @param type The field type/class; * * @return This builder. */ public SchemaBuilder field(String name, Class<?> type) { return field(name, (Class) type, Geometry.class.isAssignableFrom(type) ? crs : null); } /** * Adds a geometry field to the schema being built. * * @param name The field name. * @param type The field type/class; * @param crs The field srs/crs identifier. * * @return This builder. */ public SchemaBuilder field(String name, Class<? extends Geometry> type, String crs) { return field(name, type, Proj.crs(crs)); } /** * Adds a geometry field to the schema being built. * * @param name The field name. * @param type The field type/class; * @param crs The field crs. * * @return This builder. */ public SchemaBuilder field(String name, Class<? extends Geometry> type, CoordinateReferenceSystem crs) { return field(new Field(name, type, crs, props)); } /** * Adds a field to the schema being built. * * @param fld The field. * * @return This builder. */ public SchemaBuilder field(Field fld) { fields.add(fld); props = null; return this; } /** * Adds a collection/iterable of fields to the schema being built. * * @param flds The fields to add. * * @return This builder. */ public SchemaBuilder fields(Iterable<Field> flds) { for (Field fld : flds) { fields.add(fld); } return this; } /** * Adds fields to the schema being built described by a GeoTools style schema specification * of the form: <pre><name>:<type>[:srid=<srid>][,...]</pre> * * @param spec String describing the schema. * * @return This builder. */ public SchemaBuilder fields(String spec) { for (String field : spec.split(" *, *")) { String[] split = field.split(" *: *"); if (split.length < 1) { throw new IllegalArgumentException("field spec must have at least a name"); } String name = split[0]; Class<?> type; try { type = split.length > 1 ? classForName(split[1]) : Object.class; } catch (ClassNotFoundException e) { throw new IllegalArgumentException("illegal type", e); } CoordinateReferenceSystem crs = null; if (split.length > 2) { String[] srid = split[2].split("="); Integer srs = Integer.parseInt(srid.length > 1 ? srid[1] : srid[0]); crs = Proj.crs(srs); } fields.add(new Field(name, type, crs)); } return this; } /** * Adds fields to the schema derived from the attributes of the specified feature. * * @param f The feature. * * @return This builder. */ public SchemaBuilder fields(Feature f) { for (Map.Entry<String,Object> kv : f.map().entrySet()) { String key = kv.getKey(); Object val = kv.getValue(); if (val instanceof Geometry) { Geometry g = (Geometry) val; field(key, g.getClass(), Proj.crs(g)); } else { field(key, val != null ? val.getClass() : Object.class); } } return this; } Class<?> classForName(String name) throws ClassNotFoundException { if (name.contains(".")) { // already qualified return Class.forName(name); } // check for geometry type Type t = Geom.Type.from(name); if (t != null) { return t.getType(); } // try in java.lang return Class.forName("java.lang." + name); } /** * Adds a property to be set on the next field. * <p> * This value is discarded after the next call to <tt>field()</tt> * </p> * @param key The property key. * @param value The property value. * * @see {@link Field#property(String, Class)} */ public SchemaBuilder property(String key, Object value) { if (props == null) { props = new LinkedHashMap<String, Object>(); } props.put(key, value); return this; } /** * Returns the built schema. */ public Schema schema() { return new Schema(name, uri, fields); } /** * Create a new Schema with only the specified fields. Fields not present * in the original with be ignored. The order of the fields will be retained * except those that are removed. * * @param original The original Schema * @param fields The fields to retain * @return a new Schema with only the specified fields */ public static Schema select(Schema original, Iterable<String> fields) { List<Field> retain = new ArrayList<Field>(); for (String f : fields) { Field field = original.field(f); if (field != null) { retain.add(field); } } return new Schema(original.name, original.uri, original.crs, retain); } /** * Create a new Schema with the specified crs. * * @param original The original schema. * @param crs The crs. * * @return The new schema. */ public static Schema crs(Schema original, CoordinateReferenceSystem crs) { //TODO: override crs on fields? return new Schema(original.name, original.uri, crs, original.fields); } /** * Create a new Schema with the specified name. * * @param original The original schema. * @param name The new name. * * @return The new schema. */ public static Schema rename(Schema original, String name) { return new Schema(name, original.uri, original.crs, original.fields); } /** * Creates a new Schema with renamed fields. * * @param original The original schema. * @param renames Set of fields to rename, key specifies original field name, value specifies new name. * * @return The new schema. */ public static Schema renameFields(Schema original, Map<String,String> renames) { List<Field> fields = new ArrayList<>(); for (Field f : original) { if (renames.containsKey(f.name())) { f = new Field(renames.get(f.name()), f.type, f.crs, f.props); } fields.add(f); } return new Schema(original.name(), fields); } }