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.process.transform
10 
11 import org.geotools.data.collection.ListFeatureCollection
12 import org.geotools.data.simple.SimpleFeatureCollection
13 import org.geotools.feature.simple.{SimpleFeatureBuilder, SimpleFeatureTypeBuilder}
14 import org.geotools.process.ProcessException
15 import org.geotools.process.factory.{DescribeParameter, DescribeProcess, DescribeResult}
16 import org.locationtech.geomesa.process.GeoMesaProcess
17 import org.locationtech.geomesa.utils.collection.SelfClosingIterator
18 
19 import java.nio.charset.StandardCharsets
20 import scala.util.hashing.MurmurHash3
21 
22 trait HashAttribute {
23 
24   def transformHash(hash: Int): AnyRef
25   // note - augmentSft needs to add an attribute called 'hash'
26   def augmentSft(sft: SimpleFeatureTypeBuilder): Unit
27 
28   @throws(classOf[ProcessException])
29   @DescribeResult(name = "result", description = "Output collection")
30   def execute(@DescribeParameter(name = "data", description = "Input features")
31               obsFeatures: SimpleFeatureCollection,
32               @DescribeParameter(name = "attribute", description = "The attribute to hash on")
33               attribute: String,
34               @DescribeParameter(name = "modulo", description = "The divisor")
35               modulo: Integer): SimpleFeatureCollection = {
36 
37     val sft = obsFeatures.getSchema
38     val sftBuilder = new SimpleFeatureTypeBuilder()
39     sftBuilder.init(sft)
40     augmentSft(sftBuilder)
41     val targetSft = sftBuilder.buildFeatureType()
42     val hashIndex = targetSft.indexOf("hash")
43     val featureBuilder = new SimpleFeatureBuilder(targetSft)
44 
45     val results = new ListFeatureCollection(targetSft)
46 
47     SelfClosingIterator(obsFeatures.features()).foreach { sf =>
48       featureBuilder.reset()
49       featureBuilder.init(sf)
50       val attr = Option(sf.getAttribute(attribute)).map(_.toString).getOrElse("")
51       val hash = math.abs(MurmurHash3.bytesHash(attr.getBytes(StandardCharsets.UTF_16LE))) % modulo
52       featureBuilder.set(hashIndex, transformHash(hash))
53       results.add(featureBuilder.buildFeature(sf.getID))
54     }
55 
56     results
57   }
58 }
59 
60 @DescribeProcess(
61   title = "Hash Attribute Process",
62   description = "Adds an attribute to each SimpleFeature that hashes the configured attribute modulo the configured param"
63 )
64 class HashAttributeProcess extends GeoMesaProcess with HashAttribute {
65   override def transformHash(hash: Int): AnyRef = Int.box(hash)
66 
67   override def augmentSft(sftBuilder: SimpleFeatureTypeBuilder): Unit = {
68     sftBuilder.add("hash", classOf[Integer])
69   }
70 }
71 
72 @DescribeProcess(
73   title = "Hash Attribute Color Process",
74   description = "Adds an attribute to each SimpleFeature that hashes the configured attribute modulo the configured param and emits a color"
75 )
76 class HashAttributeColorProcess extends GeoMesaProcess with HashAttribute {
77   val colors =
78     Array[String](
79       "#6495ED",
80       "#B0C4DE",
81       "#00FFFF",
82       "#9ACD32",
83       "#00FA9A",
84       "#FFF8DC",
85       "#F5DEB3")
86 
87   override def transformHash(hash: Int): AnyRef = colors(hash % colors.length)
88 
89   override def augmentSft(sftBuilder: SimpleFeatureTypeBuilder): Unit = {
90     sftBuilder.add("hash", classOf[String])
91   }
92 }
Line Stmt Id Pos Tree Symbol Tests Code
37 1707 1755 - 1776 Apply org.geotools.feature.FeatureCollection.getSchema obsFeatures.getSchema()
38 1708 1798 - 1828 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.<init> new org.geotools.feature.simple.SimpleFeatureTypeBuilder()
39 1709 1833 - 1853 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.init sftBuilder.init(sft)
40 1710 1858 - 1880 Apply org.locationtech.geomesa.process.transform.HashAttribute.augmentSft HashAttribute.this.augmentSft(sftBuilder)
41 1711 1901 - 1930 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.buildFeatureType sftBuilder.buildFeatureType()
42 1712 1951 - 1976 Apply org.geotools.api.feature.simple.SimpleFeatureType.indexOf targetSft.indexOf("hash")
43 1713 2002 - 2037 Apply org.geotools.feature.simple.SimpleFeatureBuilder.<init> new org.geotools.feature.simple.SimpleFeatureBuilder(targetSft)
45 1714 2057 - 2093 Apply org.geotools.data.collection.ListFeatureCollection.<init> new org.geotools.data.collection.ListFeatureCollection(targetSft)
47 1715 2119 - 2141 Apply org.geotools.data.simple.SimpleFeatureCollection.features obsFeatures.features()
47 1729 2099 - 2519 Apply scala.collection.Iterator.foreach org.locationtech.geomesa.utils.collection.SelfClosingIterator.apply(obsFeatures.features()).foreach[Boolean](((sf: org.geotools.api.feature.simple.SimpleFeature) => { featureBuilder.reset(); featureBuilder.init(sf); val attr: String = scala.Option.apply[Object](sf.getAttribute(attribute)).map[String](((x$1: Object) => x$1.toString())).getOrElse[String](""); val hash: Int = scala.math.`package`.abs(scala.util.hashing.MurmurHash3.bytesHash(attr.getBytes(java.nio.charset.StandardCharsets.UTF_16LE))).%(scala.Predef.Integer2int(modulo)); featureBuilder.set(hashIndex, HashAttribute.this.transformHash(hash)); results.add(featureBuilder.buildFeature(sf.getID())) }))
48 1716 2165 - 2187 Apply org.geotools.feature.simple.SimpleFeatureBuilder.reset featureBuilder.reset()
49 1717 2194 - 2217 Apply org.geotools.feature.simple.SimpleFeatureBuilder.init featureBuilder.init(sf)
50 1718 2235 - 2299 Apply scala.Option.getOrElse scala.Option.apply[Object](sf.getAttribute(attribute)).map[String](((x$1: Object) => x$1.toString())).getOrElse[String]("")
51 1719 2362 - 2387 Select java.nio.charset.StandardCharsets.UTF_16LE java.nio.charset.StandardCharsets.UTF_16LE
51 1720 2348 - 2388 Apply java.lang.String.getBytes attr.getBytes(java.nio.charset.StandardCharsets.UTF_16LE)
51 1721 2326 - 2389 Apply scala.util.hashing.MurmurHash3.bytesHash scala.util.hashing.MurmurHash3.bytesHash(attr.getBytes(java.nio.charset.StandardCharsets.UTF_16LE))
51 1722 2393 - 2399 ApplyImplicitView scala.Predef.Integer2int scala.Predef.Integer2int(modulo)
51 1723 2317 - 2399 Apply scala.Int.% scala.math.`package`.abs(scala.util.hashing.MurmurHash3.bytesHash(attr.getBytes(java.nio.charset.StandardCharsets.UTF_16LE))).%(scala.Predef.Integer2int(modulo))
52 1724 2436 - 2455 Apply org.locationtech.geomesa.process.transform.HashAttribute.transformHash HashAttribute.this.transformHash(hash)
52 1725 2406 - 2456 Apply org.geotools.feature.simple.SimpleFeatureBuilder.set featureBuilder.set(hashIndex, HashAttribute.this.transformHash(hash))
53 1726 2503 - 2511 Apply org.geotools.api.feature.simple.SimpleFeature.getID sf.getID()
53 1727 2475 - 2512 Apply org.geotools.feature.simple.SimpleFeatureBuilder.buildFeature featureBuilder.buildFeature(sf.getID())
53 1728 2463 - 2513 Apply org.geotools.data.collection.ListFeatureCollection.add results.add(featureBuilder.buildFeature(sf.getID()))
65 1730 2840 - 2853 Apply scala.Int.box scala.Int.box(hash)
68 1731 2933 - 2973 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.add sftBuilder.add("hash", classOf[java.lang.Integer])
78 1739 3279 - 3412 ApplyToImplicitArgs scala.Array.apply scala.Array.apply[String]("#6495ED", "#B0C4DE", "#00FFFF", "#9ACD32", "#00FA9A", "#FFF8DC", "#F5DEB3")((ClassTag.apply[String](classOf[java.lang.String]): scala.reflect.ClassTag[String]))
79 1732 3300 - 3309 Literal <nosymbol> "#6495ED"
80 1733 3317 - 3326 Literal <nosymbol> "#B0C4DE"
81 1734 3334 - 3343 Literal <nosymbol> "#00FFFF"
82 1735 3351 - 3360 Literal <nosymbol> "#9ACD32"
83 1736 3368 - 3377 Literal <nosymbol> "#00FA9A"
84 1737 3385 - 3394 Literal <nosymbol> "#FFF8DC"
85 1738 3402 - 3411 Literal <nosymbol> "#F5DEB3"
87 1740 3478 - 3491 Select scala.Array.length HashAttributeColorProcess.this.colors.length
87 1741 3471 - 3491 Apply scala.Int.% hash.%(HashAttributeColorProcess.this.colors.length)
87 1742 3464 - 3492 Apply scala.Array.apply HashAttributeColorProcess.this.colors.apply(hash.%(HashAttributeColorProcess.this.colors.length))
90 1743 3572 - 3611 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.add sftBuilder.add("hash", classOf[java.lang.String])