Skip to content

Commit 5cedfd1

Browse files
authored
GEOMESA-3408 Document feature-to-feature converter (#3230)
1 parent c7e71b7 commit 5cedfd1

File tree

4 files changed

+198
-0
lines changed

4 files changed

+198
-0
lines changed

docs/user/convert/common.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ the root of the classpath. In the GeoMesa tools distribution, the files can be p
1010
`Standard Behavior <https://github.com/lightbend/config#standard-behavior>`__ for more information on how
1111
TypeSafe loads files.
1212

13+
.. _converter_sft_defs:
14+
1315
Defining SimpleFeatureTypes
1416
---------------------------
1517

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
Feature-To-Feature Converter
2+
============================
3+
4+
The feature-to-feature converter can be used to transform ``SimpleFeature``\ s from one ``SimpleFeatureType`` to another.
5+
Unlike other GeoMesa converters, the feature-to-feature converter must be invoked programmatically, as there is no
6+
native decoding of features from an input stream.
7+
8+
Configuration
9+
-------------
10+
11+
The feature-to-feature converter expects a ``type`` of ``simple-feature``. It also requires the input feature type to be
12+
defined with ``input-sft``, which must reference the name of a feature type available on the classpath - see
13+
:ref:`converter_sft_defs` for details on making the feature type available.
14+
15+
The ``fields`` of the converter can reference the attributes of the input feature type by name, using ``$`` notation. Any
16+
fields that have the same name as the input type will be automatically copied, unless they are explicitly redefined in the
17+
converter definition. The feature ID will also be copied, unless it is redefined with ``id-field``.
18+
19+
Example Usage
20+
-------------
21+
22+
Given an input feature type defined as:
23+
24+
::
25+
26+
geomesa.sfts.intype = {
27+
type-name = "intype"
28+
attributes = [
29+
{ name = "number", type = "Integer" }
30+
{ name = "color", type = "String" }
31+
{ name = "weight", type = "Double" }
32+
{ name = "geom", type = "Point" }
33+
]
34+
}
35+
36+
And an output feature type defined as:
37+
38+
::
39+
40+
geomesa.sfts.outtype = {
41+
type-name = "outtype"
42+
attributes = [
43+
{ name = "number", type = "Integer" }
44+
{ name = "color", type = "String" }
45+
{ name = "weight", type = "Double" }
46+
{ name = "numberx2", type = "Integer" }
47+
{ name = "geom", type = "Point" }
48+
]
49+
}
50+
51+
The following example will copy the attributes of the input features, while adding a new attribute (``numberx2``) derived
52+
from one of the input fields:
53+
54+
::
55+
56+
geomesa.converters.myconverter = {
57+
type = "simple-feature"
58+
input-sft = "intype"
59+
fields = [
60+
// note: number, color, weight, and geom will be auto-copied since they exist in both input and output types
61+
{ name = "numberx2", transform = "add($number, $number)::int" }
62+
]
63+
}
64+
65+
.. tabs::
66+
67+
.. code-tab:: scala
68+
69+
import org.geotools.api.feature.simple.SimpleFeature
70+
import org.locationtech.geomesa.convert.ConverterConfigLoader
71+
import org.locationtech.geomesa.convert2.simplefeature.FeatureToFeatureConverter
72+
import org.locationtech.geomesa.utils.collection.CloseableIterator
73+
import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypeLoader
74+
75+
val sft = SimpleFeatureTypeLoader.sftForName("outtype").getOrElse {
76+
throw new RuntimeException("Could not load feature type")
77+
}
78+
val conf = ConverterConfigLoader.configForName("myconverter").getOrElse {
79+
throw new RuntimeException("Could not load converter definition")
80+
}
81+
val converter = FeatureToFeatureConverter(sft, conf)
82+
try {
83+
val features: Iterator[SimpleFeature] = ??? // list of input features to transform
84+
val iter = converter.convert(CloseableIterator(features))
85+
try {
86+
iter.foreach(???) // do something with the conversion result
87+
} finally {
88+
iter.close()
89+
}
90+
} finally {
91+
converter.close() // clean up any resources associated with your converter
92+
}
93+
94+
.. code-tab:: java
95+
96+
import com.typesafe.config.Config;
97+
import org.geotools.api.feature.simple.SimpleFeature;
98+
import org.geotools.api.feature.simple.SimpleFeatureType;
99+
import org.locationtech.geomesa.convert.ConverterConfigLoader;
100+
import org.locationtech.geomesa.convert.EvaluationContext;
101+
import org.locationtech.geomesa.convert2.simplefeature.FeatureToFeatureConverter;
102+
import org.locationtech.geomesa.utils.collection.CloseableIterator;
103+
import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypeLoader;
104+
105+
import java.util.List;
106+
import java.util.Map;
107+
108+
SimpleFeatureType outsft = SimpleFeatureTypeLoader.sftForName("outtype").get();
109+
Config parserConf = ConverterConfigLoader.configForName("myconverter").get();
110+
111+
List<SimpleFeature> features = ...; // list of input features to transform
112+
113+
// use try-with-resources to clean up the converter when we're done
114+
try (FeatureToFeatureConverter converter = FeatureToFeatureConverter.apply(outsft, parserConf)) {
115+
EvaluationContext context = converter.createEvaluationContext(Map.of());
116+
try (CloseableIterator<SimpleFeature> iter = converter.convert(CloseableIterator.apply(features.iterator()), ec)) {
117+
while (iter.hasNext()) {
118+
iter.next(); // do something with the conversion result
119+
}
120+
}
121+
}

docs/user/convert/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ details.
3232
fixed_width
3333
jdbc
3434
composite
35+
feature_to_feature
3536
premade/index
3637
function_overview
3738
function_usage
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/***********************************************************************
2+
* Copyright (c) 2013-2024 Commonwealth Computer Research, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Apache License, Version 2.0
5+
* which accompanies this distribution and is available at
6+
* http://www.opensource.org/licenses/apache2.0.php.
7+
***********************************************************************/
8+
9+
package org.locationtech.geomesa.convert2.simplefeature;
10+
11+
import com.typesafe.config.Config;
12+
import com.typesafe.config.ConfigFactory;
13+
import org.geotools.api.feature.simple.SimpleFeature;
14+
import org.geotools.api.feature.simple.SimpleFeatureType;
15+
import org.geotools.feature.simple.SimpleFeatureBuilder;
16+
import org.junit.Assert;
17+
import org.junit.Test;
18+
import org.locationtech.geomesa.convert.ConverterConfigLoader;
19+
import org.locationtech.geomesa.convert.EvaluationContext;
20+
import org.locationtech.geomesa.utils.collection.CloseableIterator;
21+
import org.locationtech.geomesa.utils.geotools.SimpleFeatureTypeLoader;
22+
import org.locationtech.geomesa.utils.interop.SimpleFeatureTypes;
23+
import org.locationtech.geomesa.utils.interop.WKTUtils;
24+
import org.locationtech.jts.geom.Geometry;
25+
26+
import java.io.IOException;
27+
import java.util.List;
28+
import java.util.Map;
29+
30+
public class FeatureToFeatureJavaTest {
31+
32+
@Test
33+
public void testJavaApi() {
34+
Config outConfPoint = ConfigFactory.parseString(
35+
"{ type-name = outtype, attributes = [" +
36+
"{ name = number, type = Integer }," +
37+
"{ name = color, type = String }," +
38+
"{ name = weight, type = Double }," +
39+
"{ name = numberx2, type = Integer }," +
40+
"{ name = geom, type = Point }" +
41+
"]}");
42+
43+
Config parserConf = ConfigFactory.parseString(
44+
"{ type = simple-feature, input-sft = intype, fields = [" +
45+
"{ name = number, transform = \"$number\" }," +
46+
"{ name = color , transform = \"$color\" }," +
47+
"{ name = weight, transform = \"$weight\" }," +
48+
"{ name = geom, transform = \"$geom\" }," +
49+
"{ name = numberx2, transform = \"add($number, $number)::int\" }" +
50+
"]}");
51+
52+
SimpleFeatureType insft = SimpleFeatureTypeLoader.sftForName("intype").get();
53+
SimpleFeatureType outsft = SimpleFeatureTypes.createType(outConfPoint);
54+
ConverterConfigLoader.configForName("myconverter");
55+
FeatureToFeatureConverter converter = FeatureToFeatureConverter.apply(outsft, parserConf);
56+
Assert.assertNotNull(converter);
57+
58+
Geometry pt = WKTUtils.read("POINT(0 0)");
59+
SimpleFeatureBuilder builder = new SimpleFeatureBuilder(insft);
60+
builder.reset();
61+
builder.addAll(1, "blue", 10.0, pt);
62+
SimpleFeature sf = builder.buildFeature("1");
63+
64+
EvaluationContext ec = converter.createEvaluationContext(Map.of());
65+
try(CloseableIterator<SimpleFeature> res = converter.convert(CloseableIterator.apply(List.of(sf).iterator()), ec)) {
66+
Assert.assertTrue(res.hasNext());
67+
SimpleFeature next = res.next();
68+
Assert.assertFalse(res.hasNext());
69+
Assert.assertEquals(2, next.getAttribute("numberx2"));
70+
} catch (IOException e) {
71+
throw new RuntimeException(e);
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)