diff --git a/docs/user/convert/common.rst b/docs/user/convert/common.rst index 8c22300652c0..5110b6adea99 100644 --- a/docs/user/convert/common.rst +++ b/docs/user/convert/common.rst @@ -10,6 +10,8 @@ the root of the classpath. In the GeoMesa tools distribution, the files can be p `Standard Behavior `__ for more information on how TypeSafe loads files. +.. _converter_sft_defs: + Defining SimpleFeatureTypes --------------------------- diff --git a/docs/user/convert/feature_to_feature.rst b/docs/user/convert/feature_to_feature.rst new file mode 100644 index 000000000000..07b4f7006e8d --- /dev/null +++ b/docs/user/convert/feature_to_feature.rst @@ -0,0 +1,121 @@ +Feature-To-Feature Converter +============================ + +The feature-to-feature converter can be used to transform ``SimpleFeature``\ s from one ``SimpleFeatureType`` to another. +Unlike other GeoMesa converters, the feature-to-feature converter must be invoked programmatically, as there is no +native decoding of features from an input stream. + +Configuration +------------- + +The feature-to-feature converter expects a ``type`` of ``simple-feature``. It also requires the input feature type to be +defined with ``input-sft``, which must reference the name of a feature type available on the classpath - see +:ref:`converter_sft_defs` for details on making the feature type available. + +The ``fields`` of the converter can reference the attributes of the input feature type by name, using ``$`` notation. Any +fields that have the same name as the input type will be automatically copied, unless they are explicitly redefined in the +converter definition. The feature ID will also be copied, unless it is redefined with ``id-field``. + +Example Usage +------------- + +Given an input feature type defined as: + +:: + + geomesa.sfts.intype = { + type-name = "intype" + attributes = [ + { name = "number", type = "Integer" } + { name = "color", type = "String" } + { name = "weight", type = "Double" } + { name = "geom", type = "Point" } + ] + } + +And an output feature type defined as: + +:: + + geomesa.sfts.outtype = { + type-name = "outtype" + attributes = [ + { name = "number", type = "Integer" } + { name = "color", type = "String" } + { name = "weight", type = "Double" } + { name = "numberx2", type = "Integer" } + { name = "geom", type = "Point" } + ] + } + +The following example will copy the attributes of the input features, while adding a new attribute (``numberx2``) derived +from one of the input fields: + +:: + + geomesa.converters.myconverter = { + type = "simple-feature" + input-sft = "intype" + fields = [ + // note: number, color, weight, and geom will be auto-copied since they exist in both input and output types + { name = "numberx2", transform = "add($number, $number)::int" } + ] + } + +.. tabs:: + + .. code-tab:: scala + + import org.geotools.api.feature.simple.SimpleFeature + import org.locationtech.geomesa.convert.ConverterConfigLoader + import org.locationtech.geomesa.convert2.simplefeature.FeatureToFeatureConverter + import org.locationtech.geomesa.utils.collection.CloseableIterator + import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypeLoader + + val sft = SimpleFeatureTypeLoader.sftForName("outtype").getOrElse { + throw new RuntimeException("Could not load feature type") + } + val conf = ConverterConfigLoader.configForName("myconverter").getOrElse { + throw new RuntimeException("Could not load converter definition") + } + val converter = FeatureToFeatureConverter(sft, conf) + try { + val features: Iterator[SimpleFeature] = ??? // list of input features to transform + val iter = converter.convert(CloseableIterator(features)) + try { + iter.foreach(???) // do something with the conversion result + } finally { + iter.close() + } + } finally { + converter.close() // clean up any resources associated with your converter + } + + .. code-tab:: java + + import com.typesafe.config.Config; + import org.geotools.api.feature.simple.SimpleFeature; + import org.geotools.api.feature.simple.SimpleFeatureType; + import org.locationtech.geomesa.convert.ConverterConfigLoader; + import org.locationtech.geomesa.convert.EvaluationContext; + import org.locationtech.geomesa.convert2.simplefeature.FeatureToFeatureConverter; + import org.locationtech.geomesa.utils.collection.CloseableIterator; + import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypeLoader; + + import java.util.List; + import java.util.Map; + + SimpleFeatureType outsft = SimpleFeatureTypeLoader.sftForName("outtype").get(); + Config parserConf = ConverterConfigLoader.configForName("myconverter").get(); + + List features = ...; // list of input features to transform + + // use try-with-resources to clean up the converter when we're done + try (FeatureToFeatureConverter converter = FeatureToFeatureConverter.apply(outsft, parserConf)) { + EvaluationContext context = converter.createEvaluationContext(Map.of()); + try (CloseableIterator iter = converter.convert(CloseableIterator.apply(features.iterator()), ec)) { + while (iter.hasNext()) { + iter.next(); // do something with the conversion result + } + } + } diff --git a/docs/user/convert/index.rst b/docs/user/convert/index.rst index 8eed2fe63c1d..575824b17559 100644 --- a/docs/user/convert/index.rst +++ b/docs/user/convert/index.rst @@ -32,6 +32,7 @@ details. fixed_width jdbc composite + feature_to_feature premade/index function_overview function_usage diff --git a/geomesa-convert/geomesa-convert-simplefeature/src/test/java/org/locationtech/geomesa/convert2/simplefeature/FeatureToFeatureJavaTest.java b/geomesa-convert/geomesa-convert-simplefeature/src/test/java/org/locationtech/geomesa/convert2/simplefeature/FeatureToFeatureJavaTest.java new file mode 100644 index 000000000000..7bc4d6a941a3 --- /dev/null +++ b/geomesa-convert/geomesa-convert-simplefeature/src/test/java/org/locationtech/geomesa/convert2/simplefeature/FeatureToFeatureJavaTest.java @@ -0,0 +1,74 @@ +/*********************************************************************** + * Copyright (c) 2013-2024 Commonwealth Computer Research, Inc. + * 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 org.locationtech.geomesa.convert2.simplefeature; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.api.feature.simple.SimpleFeatureType; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.junit.Assert; +import org.junit.Test; +import org.locationtech.geomesa.convert.ConverterConfigLoader; +import org.locationtech.geomesa.convert.EvaluationContext; +import org.locationtech.geomesa.utils.collection.CloseableIterator; +import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypeLoader; +import org.locationtech.geomesa.utils.interop.SimpleFeatureTypes; +import org.locationtech.geomesa.utils.interop.WKTUtils; +import org.locationtech.jts.geom.Geometry; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class FeatureToFeatureJavaTest { + + @Test + public void testJavaApi() { + Config outConfPoint = ConfigFactory.parseString( + "{ type-name = outtype, attributes = [" + + "{ name = number, type = Integer }," + + "{ name = color, type = String }," + + "{ name = weight, type = Double }," + + "{ name = numberx2, type = Integer }," + + "{ name = geom, type = Point }" + + "]}"); + + Config parserConf = ConfigFactory.parseString( + "{ type = simple-feature, input-sft = intype, fields = [" + + "{ name = number, transform = \"$number\" }," + + "{ name = color , transform = \"$color\" }," + + "{ name = weight, transform = \"$weight\" }," + + "{ name = geom, transform = \"$geom\" }," + + "{ name = numberx2, transform = \"add($number, $number)::int\" }" + + "]}"); + + SimpleFeatureType insft = SimpleFeatureTypeLoader.sftForName("intype").get(); + SimpleFeatureType outsft = SimpleFeatureTypes.createType(outConfPoint); + ConverterConfigLoader.configForName("myconverter"); + FeatureToFeatureConverter converter = FeatureToFeatureConverter.apply(outsft, parserConf); + Assert.assertNotNull(converter); + + Geometry pt = WKTUtils.read("POINT(0 0)"); + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(insft); + builder.reset(); + builder.addAll(1, "blue", 10.0, pt); + SimpleFeature sf = builder.buildFeature("1"); + + EvaluationContext ec = converter.createEvaluationContext(Map.of()); + try(CloseableIterator res = converter.convert(CloseableIterator.apply(List.of(sf).iterator()), ec)) { + Assert.assertTrue(res.hasNext()); + SimpleFeature next = res.next(); + Assert.assertFalse(res.hasNext()); + Assert.assertEquals(2, next.getAttribute("numberx2")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +}