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.analytic
10 
11 import com.typesafe.scalalogging.LazyLogging
12 import org.geotools.api.data.{Query, SimpleFeatureSource}
13 import org.geotools.api.feature.Feature
14 import org.geotools.api.feature.`type`.AttributeDescriptor
15 import org.geotools.api.feature.simple.{SimpleFeature, SimpleFeatureType}
16 import org.geotools.api.filter.Filter
17 import org.geotools.api.util.ProgressListener
18 import org.geotools.data.collection.ListFeatureCollection
19 import org.geotools.data.simple.SimpleFeatureCollection
20 import org.geotools.feature.simple.{SimpleFeatureBuilder, SimpleFeatureTypeBuilder}
21 import org.geotools.feature.visitor.{AbstractCalcResult, CalcResult}
22 import org.geotools.process.factory.{DescribeParameter, DescribeProcess, DescribeResult}
23 import org.locationtech.geomesa.filter.factory.FastFilterFactory
24 import org.locationtech.geomesa.index.conf.QueryHints
25 import org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection
26 import org.locationtech.geomesa.index.index.attribute.AttributeIndex
27 import org.locationtech.geomesa.index.iterators.StatsScan
28 import org.locationtech.geomesa.index.process.GeoMesaProcessVisitor
29 import org.locationtech.geomesa.process.GeoMesaProcess
30 import org.locationtech.geomesa.utils.collection.SelfClosingIterator
31 import org.locationtech.geomesa.utils.geotools.RichAttributeDescriptors.RichAttributeDescriptor
32 import org.locationtech.geomesa.utils.stats.{EnumerationStat, Stat}
33 
34 import scala.collection.JavaConverters._
35 import scala.collection.mutable
36 
37 @DescribeProcess(title = "Geomesa Unique",
38   description = "Finds unique attributes values, optimized for GeoMesa")
39 class UniqueProcess extends GeoMesaProcess with LazyLogging {
40 
41   @DescribeResult(name = "result",
42     description = "Feature collection with an attribute containing the unique values")
43   def execute(
44     @DescribeParameter(name = "features", description = "Input feature collection")
45     features: SimpleFeatureCollection,
46     @DescribeParameter(name = "attribute", description = "Attribute whose unique values are extracted")
47     attribute: String,
48     @DescribeParameter(name = "filter", min = 0, description = "The filter to apply to the feature collection")
49     filter: Filter,
50     @DescribeParameter(name = "histogram", min = 0, description = "Create a histogram of attribute values")
51     histogram: java.lang.Boolean,
52     @DescribeParameter(name = "sort", min = 0, description = "Sort results - allowed to be ASC or DESC")
53     sort: String,
54     @DescribeParameter(name = "sortByCount", min = 0, description = "Sort by histogram counts instead of attribute values")
55     sortByCount: java.lang.Boolean,
56     progressListener: ProgressListener): SimpleFeatureCollection = {
57 
58     val attributeDescriptor = features
59         .getSchema
60         .getAttributeDescriptors
61         .asScala
62         .find(_.getLocalName == attribute)
63         .getOrElse(throw new IllegalArgumentException(s"Attribute $attribute does not exist in feature schema."))
64 
65     val hist = Option(histogram).exists(_.booleanValue)
66     val sortBy = Option(sortByCount).exists(_.booleanValue)
67 
68     val visitor = new AttributeVisitor(features, attributeDescriptor, Option(filter).filter(_ != Filter.INCLUDE), hist)
69     GeoMesaFeatureCollection.visit(features, visitor, progressListener)
70     val uniqueValues = visitor.getResult.attributes
71 
72     val binding = attributeDescriptor.getType.getBinding
73     UniqueProcess.createReturnCollection(uniqueValues, binding, hist, Option(sort), sortBy)
74   }
75 }
76 
77 object UniqueProcess {
78 
79   val SftName = "UniqueValue"
80   val AttributeValue = "value"
81   val AttributeCount = "count"
82 
83   /**
84     * Duplicates output format from geotools UniqueProcess
85     *
86     * @param uniqueValues values
87     * @param binding value binding
88     * @param histogram include counts or just values
89     * @param sort sort
90     * @param sortByCount sort by count or by value
91     * @return
92     */
93   def createReturnCollection(uniqueValues: Map[Any, Long],
94                              binding: Class[_],
95                              histogram: Boolean,
96                              sort: Option[String],
97                              sortByCount: Boolean): SimpleFeatureCollection = {
98 
99     val ft = createUniqueSft(binding, histogram)
100 
101     val sfb = new SimpleFeatureBuilder(ft)
102 
103     val result = new ListFeatureCollection(ft)
104 
105     // if sorting was requested do it here, otherwise return results in iterator order
106     val sorted = sort.map { s =>
107       if (sortByCount) {
108         val ordering = if (s.equalsIgnoreCase("desc")) Ordering[Long].reverse else Ordering[Long]
109         uniqueValues.iterator.toList.sortBy(_._2)(ordering)
110       } else {
111         val ordering = if (s.equalsIgnoreCase("desc")) Ordering[String].reverse else Ordering[String]
112         uniqueValues.iterator.toList.sortBy(_._1.toString)(ordering)
113       }
114     }.getOrElse(uniqueValues.iterator)
115 
116     // histogram includes extra 'count' attribute
117     val addFn = if (histogram) (key: Any, value: Long) => {
118       sfb.add(key)
119       sfb.add(value)
120       result.add(sfb.buildFeature(null))
121     } else (key: Any, _: Long) => {
122       sfb.add(key)
123       result.add(sfb.buildFeature(null))
124     }
125 
126     sorted.foreach { case (key, value) => addFn(key, value) }
127 
128     result
129   }
130 
131   /**
132     * Based on geotools UniqueProcess simple feature type
133     *
134     * @param binding class of attribute
135     * @param histogram return counts or not
136     * @return
137     */
138   def createUniqueSft(binding: Class[_], histogram: Boolean): SimpleFeatureType = {
139     val sftb = new SimpleFeatureTypeBuilder
140     sftb.add(AttributeValue, binding)
141     if (histogram) {
142       // histogram includes extra 'count' attribute
143       sftb.add(AttributeCount, classOf[java.lang.Long])
144     }
145 
146     sftb.setName(SftName)
147     sftb.buildFeatureType
148   }
149 }
150 
151 /**
152  * Visitor that tracks unique attribute values and counts
153  *
154  * @param features features to evaluate
155  * @param attributeDescriptor attribute to evaluate
156  * @param filter optional filter to apply to features before evaluating
157  * @param histogram return counts or not
158  */
159 class AttributeVisitor(val features: SimpleFeatureCollection,
160                        val attributeDescriptor: AttributeDescriptor,
161                        val filter: Option[Filter],
162                        histogram: Boolean) extends GeoMesaProcessVisitor with LazyLogging {
163 
164   import scala.collection.JavaConverters._
165 
166   private val attribute    = attributeDescriptor.getLocalName
167   private val uniqueValues = mutable.Map.empty[Any, Long].withDefaultValue(0)
168 
169   private var attributeIdx: Int = -1
170 
171   // normally handled in our query planner, but we are going to use the filter directly here
172   private lazy val manualFilter = filter.map(FastFilterFactory.optimize(features.getSchema, _))
173 
174   private def getAttribute[T](f: SimpleFeature): T = {
175     if (attributeIdx == -1) {
176       attributeIdx = f.getType.indexOf(attribute)
177     }
178     f.getAttribute(attributeIdx).asInstanceOf[T]
179   }
180 
181   private def addSingularValue(f: SimpleFeature): Unit = {
182     val value = getAttribute[AnyRef](f)
183     if (value != null) {
184       uniqueValues(value) += 1
185     }
186   }
187 
188   private def addMultiValue(f: SimpleFeature): Unit = {
189     val values = getAttribute[java.util.Collection[_]](f)
190     if (values != null) {
191       values.asScala.foreach(uniqueValues(_) += 1)
192     }
193   }
194 
195   private val addValue: SimpleFeature => Unit =
196     if (attributeDescriptor.isList) { addMultiValue } else { addSingularValue }
197 
198   // non-optimized visit
199   override def visit(feature: Feature): Unit = {
200     val f = feature.asInstanceOf[SimpleFeature]
201     if (manualFilter.forall(_.evaluate(f))) {
202       addValue(f)
203     }
204   }
205 
206   override def getResult: AttributeResult = new AttributeResult(uniqueValues.toMap)
207 
208   override def execute(source: SimpleFeatureSource, query: Query): Unit = {
209 
210     import org.locationtech.geomesa.filter.mergeFilters
211 
212     logger.debug(s"Running Geomesa histogram process on source type ${source.getClass.getName}")
213 
214     // combine filters from this process and any input collection
215     filter.foreach(f => query.setFilter(mergeFilters(query.getFilter, f)))
216 
217     val sft = source.getSchema
218 
219     val enumerated = if (attributeDescriptor.isMultiValued) {
220       // stats don't support list types
221       uniqueV5(source, query)
222     } else {
223       // TODO if !histogram, we could write a new unique skipping iterator
224       query.getHints.put(QueryHints.STATS_STRING, Stat.Enumeration(attribute))
225       query.getHints.put(QueryHints.ENCODE_STATS, java.lang.Boolean.TRUE)
226 
227       // execute the query
228       val reader = source.getFeatures(query).features()
229 
230       val enumeration = try {
231         // stats should always return exactly one result, even if there are no features in the table
232         val encoded = reader.next.getAttribute(0).asInstanceOf[String]
233         StatsScan.decodeStat(sft)(encoded).asInstanceOf[EnumerationStat[Any]]
234       } finally {
235         reader.close()
236       }
237 
238       enumeration.frequencies
239     }
240 
241     uniqueValues.clear()
242     enumerated.foreach { case (k, v) => uniqueValues.put(k, v) }
243   }
244 
245   private def uniqueV5(source: SimpleFeatureSource, query: Query): Iterable[(Any, Long)] = {
246     // only return the attribute we are interested in to reduce bandwidth
247     query.setPropertyNames(Seq(attribute).asJava)
248 
249     // if there is no filter, try to force an attribute scan - should be fastest query
250     if (query.getFilter == Filter.INCLUDE && AttributeIndex.indexed(features.getSchema, attribute)) {
251       query.setFilter(AttributeVisitor.getIncludeAttributeFilter(attribute))
252     }
253 
254     // execute the query
255     SelfClosingIterator(source.getFeatures(query).features()).foreach(addValue)
256     uniqueValues.toMap
257   }
258 }
259 
260 object AttributeVisitor {
261 
262   import org.locationtech.geomesa.filter.ff
263 
264   /**
265    * Returns a filter that is equivalent to Filter.INCLUDE, but against the attribute index.
266    *
267    * @param attribute attribute to query
268    * @return
269    */
270   def getIncludeAttributeFilter(attribute: String): Filter =
271     ff.greaterOrEqual(ff.property(attribute), ff.literal(""))
272 }
273 
274 /**
275  * Result class to hold the attribute histogram
276  *
277  * @param attributes result
278  */
279 class AttributeResult(val attributes: Map[Any, Long]) extends AbstractCalcResult {
280 
281   override def getValue: java.util.Map[Any, Long] = attributes.asJava
282 
283   override def isCompatible(targetResults: CalcResult): Boolean =
284     targetResults.isInstanceOf[AttributeResult] || targetResults == CalcResult.NULL_RESULT
285 
286   override def merge(resultsToAdd: CalcResult): CalcResult = {
287     if (!isCompatible(resultsToAdd)) {
288       throw new IllegalArgumentException("Parameter is not a compatible type")
289     } else if (resultsToAdd == CalcResult.NULL_RESULT) {
290       this
291     } else if (resultsToAdd.isInstanceOf[AttributeResult]) {
292       val toAdd = resultsToAdd.getValue.asInstanceOf[Map[Any, Long]]
293       // note ++ on maps will get all keys with second maps values if exists, if not first map values
294       val merged = attributes ++ toAdd.map {
295         case (attr, count) => attr -> (count + attributes.getOrElse(attr, 0L))
296       }
297       new AttributeResult(merged)
298     } else {
299       throw new IllegalArgumentException(
300         "The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.")
301     }
302   }
303 }
Line Stmt Id Pos Tree Symbol Tests Code
60 376 3204 - 3264 Apply org.geotools.api.feature.simple.SimpleFeatureType.getAttributeDescriptors features.getSchema().getAttributeDescriptors()
62 377 3296 - 3323 Apply java.lang.Object.== x$1.getLocalName().==(attribute)
63 378 3344 - 3437 Throw <nosymbol> throw new scala.`package`.IllegalArgumentException(scala.StringContext.apply("Attribute ", " does not exist in feature schema.").s(attribute))
63 379 3204 - 3438 Apply scala.Option.getOrElse scala.collection.JavaConverters.asScalaBufferConverter[org.geotools.api.feature.type.AttributeDescriptor](features.getSchema().getAttributeDescriptors()).asScala.find(((x$1: org.geotools.api.feature.type.AttributeDescriptor) => x$1.getLocalName().==(attribute))).getOrElse[org.geotools.api.feature.type.AttributeDescriptor](throw new scala.`package`.IllegalArgumentException(scala.StringContext.apply("Attribute ", " does not exist in feature schema.").s(attribute)))
65 380 3480 - 3494 Apply java.lang.Boolean.booleanValue x$2.booleanValue()
65 381 3455 - 3495 Apply scala.Option.exists scala.Option.apply[Boolean](histogram).exists(((x$2: Boolean) => x$2.booleanValue()))
66 382 3540 - 3554 Apply java.lang.Boolean.booleanValue x$3.booleanValue()
66 383 3513 - 3555 Apply scala.Option.exists scala.Option.apply[Boolean](sortByCount).exists(((x$3: Boolean) => x$3.booleanValue()))
68 384 3654 - 3668 Select org.geotools.api.filter.Filter.INCLUDE org.geotools.api.filter.Filter.INCLUDE
68 385 3649 - 3668 Apply java.lang.Object.!= x$4.!=(org.geotools.api.filter.Filter.INCLUDE)
68 386 3627 - 3669 Apply scala.Option.filter scala.Option.apply[org.geotools.api.filter.Filter](filter).filter(((x$4: org.geotools.api.filter.Filter) => x$4.!=(org.geotools.api.filter.Filter.INCLUDE)))
68 387 3575 - 3676 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.<init> new AttributeVisitor(features, attributeDescriptor, scala.Option.apply[org.geotools.api.filter.Filter](filter).filter(((x$4: org.geotools.api.filter.Filter) => x$4.!=(org.geotools.api.filter.Filter.INCLUDE))), hist)
69 388 3681 - 3748 Apply org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection.visit org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection.visit(features, visitor, progressListener)
70 389 3772 - 3800 Select org.locationtech.geomesa.process.analytic.AttributeResult.attributes visitor.getResult().attributes
72 390 3820 - 3858 Apply org.geotools.api.feature.type.PropertyType.getBinding attributeDescriptor.getType().getBinding()
73 391 3929 - 3941 Apply scala.Option.apply scala.Option.apply[String](sort)
73 392 3863 - 3950 Apply org.locationtech.geomesa.process.analytic.UniqueProcess.createReturnCollection UniqueProcess.createReturnCollection(uniqueValues, binding, hist, scala.Option.apply[String](sort), sortBy)
79 393 3998 - 4011 Literal <nosymbol> "UniqueValue"
80 394 4035 - 4042 Literal <nosymbol> "value"
81 395 4066 - 4073 Literal <nosymbol> "count"
99 396 4663 - 4698 Apply org.locationtech.geomesa.process.analytic.UniqueProcess.createUniqueSft UniqueProcess.this.createUniqueSft(binding, histogram)
101 397 4714 - 4742 Apply org.geotools.feature.simple.SimpleFeatureBuilder.<init> new org.geotools.feature.simple.SimpleFeatureBuilder(ft)
103 398 4761 - 4790 Apply org.geotools.data.collection.ListFeatureCollection.<init> new org.geotools.data.collection.ListFeatureCollection(ft)
107 407 4935 - 5102 Block <nosymbol> { val ordering: scala.math.Ordering[Long] = if (s.equalsIgnoreCase("desc")) scala.`package`.Ordering.apply[Long](math.this.Ordering.Long).reverse else scala.`package`.Ordering.apply[Long](math.this.Ordering.Long); uniqueValues.iterator.toList.sortBy[Long](((x$5: (Any, Long)) => x$5._2))(ordering) }
108 399 4964 - 4990 Apply java.lang.String.equalsIgnoreCase s.equalsIgnoreCase("desc")
108 400 5000 - 5000 Select scala.math.Ordering.Long math.this.Ordering.Long
108 401 4992 - 5014 Select scala.math.Ordering.reverse scala.`package`.Ordering.apply[Long](math.this.Ordering.Long).reverse
108 402 4992 - 5014 Block scala.math.Ordering.reverse scala.`package`.Ordering.apply[Long](math.this.Ordering.Long).reverse
108 403 5028 - 5028 Select scala.math.Ordering.Long math.this.Ordering.Long
108 404 5020 - 5034 ApplyToImplicitArgs scala.math.Ordering.apply scala.`package`.Ordering.apply[Long](math.this.Ordering.Long)
108 405 5020 - 5034 Block scala.math.Ordering.apply scala.`package`.Ordering.apply[Long](math.this.Ordering.Long)
109 406 5043 - 5094 Apply scala.collection.SeqLike.sortBy uniqueValues.iterator.toList.sortBy[Long](((x$5: (Any, Long)) => x$5._2))(ordering)
110 416 5108 - 5288 Block <nosymbol> { val ordering: scala.math.Ordering[String] = if (s.equalsIgnoreCase("desc")) scala.`package`.Ordering.apply[String](math.this.Ordering.String).reverse else scala.`package`.Ordering.apply[String](math.this.Ordering.String); uniqueValues.iterator.toList.sortBy[String](((x$6: (Any, Long)) => x$6._1.toString()))(ordering) }
111 408 5137 - 5163 Apply java.lang.String.equalsIgnoreCase s.equalsIgnoreCase("desc")
111 409 5173 - 5173 Select scala.math.Ordering.String math.this.Ordering.String
111 410 5165 - 5189 Select scala.math.Ordering.reverse scala.`package`.Ordering.apply[String](math.this.Ordering.String).reverse
111 411 5165 - 5189 Block scala.math.Ordering.reverse scala.`package`.Ordering.apply[String](math.this.Ordering.String).reverse
111 412 5203 - 5203 Select scala.math.Ordering.String math.this.Ordering.String
111 413 5195 - 5211 ApplyToImplicitArgs scala.math.Ordering.apply scala.`package`.Ordering.apply[String](math.this.Ordering.String)
111 414 5195 - 5211 Block scala.math.Ordering.apply scala.`package`.Ordering.apply[String](math.this.Ordering.String)
112 415 5220 - 5280 Apply scala.collection.SeqLike.sortBy uniqueValues.iterator.toList.sortBy[String](((x$6: (Any, Long)) => x$6._1.toString()))(ordering)
114 417 5305 - 5326 Select scala.collection.MapLike.iterator uniqueValues.iterator
114 418 4896 - 5327 Apply scala.Option.getOrElse sort.map[List[(Any, Long)]](((s: String) => if (sortByCount) { val ordering: scala.math.Ordering[Long] = if (s.equalsIgnoreCase("desc")) scala.`package`.Ordering.apply[Long](math.this.Ordering.Long).reverse else scala.`package`.Ordering.apply[Long](math.this.Ordering.Long); uniqueValues.iterator.toList.sortBy[Long](((x$5: (Any, Long)) => x$5._2))(ordering) } else { val ordering: scala.math.Ordering[String] = if (s.equalsIgnoreCase("desc")) scala.`package`.Ordering.apply[String](math.this.Ordering.String).reverse else scala.`package`.Ordering.apply[String](math.this.Ordering.String); uniqueValues.iterator.toList.sortBy[String](((x$6: (Any, Long)) => x$6._1.toString()))(ordering) })).getOrElse[scala.collection.TraversableOnce[(Any, Long)]{def seq: scala.collection.TraversableOnce[(Any, Long)]{def seq: scala.collection.TraversableOnce[(Any, Long)]{def seq: scala.collection.TraversableOnce[(Any, Long)]}}}](uniqueValues.iterator)
117 423 5410 - 5525 Function org.locationtech.geomesa.process.analytic.UniqueProcess.$anonfun ((key: Any, value: Long) => { sfb.add(key); sfb.add(value); result.add(sfb.buildFeature(null)) })
118 419 5445 - 5457 Apply org.geotools.feature.simple.SimpleFeatureBuilder.add sfb.add(key)
119 420 5464 - 5478 Apply org.geotools.feature.simple.SimpleFeatureBuilder.add sfb.add(value)
120 421 5496 - 5518 Apply org.geotools.feature.simple.SimpleFeatureBuilder.buildFeature sfb.buildFeature(null)
120 422 5485 - 5519 Apply org.geotools.data.collection.ListFeatureCollection.add result.add(sfb.buildFeature(null))
121 427 5531 - 5621 Function org.locationtech.geomesa.process.analytic.UniqueProcess.$anonfun ((key: Any, x$7: Long) => { sfb.add(key); result.add(sfb.buildFeature(null)) })
122 424 5562 - 5574 Apply org.geotools.feature.simple.SimpleFeatureBuilder.add sfb.add(key)
123 425 5592 - 5614 Apply org.geotools.feature.simple.SimpleFeatureBuilder.buildFeature sfb.buildFeature(null)
123 426 5581 - 5615 Apply org.geotools.data.collection.ListFeatureCollection.add result.add(sfb.buildFeature(null))
126 428 5665 - 5682 Apply scala.Function2.apply addFn.apply(key, value)
126 429 5665 - 5682 Block scala.Function2.apply addFn.apply(key, value)
126 430 5627 - 5684 Apply scala.collection.TraversableOnce.foreach sorted.foreach[Boolean](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((key @ _), (value @ _)) => addFn.apply(key, value) }))
139 431 5976 - 6004 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.<init> new org.geotools.feature.simple.SimpleFeatureTypeBuilder()
140 432 6018 - 6032 Select org.locationtech.geomesa.process.analytic.UniqueProcess.AttributeValue UniqueProcess.this.AttributeValue
140 433 6009 - 6042 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.add sftb.add(UniqueProcess.this.AttributeValue, binding)
141 438 6047 - 6047 Literal <nosymbol> ()
141 439 6047 - 6047 Block <nosymbol> ()
143 434 6131 - 6145 Select org.locationtech.geomesa.process.analytic.UniqueProcess.AttributeCount UniqueProcess.this.AttributeCount
143 435 6147 - 6170 Literal <nosymbol> classOf[java.lang.Long]
143 436 6122 - 6171 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.add sftb.add(UniqueProcess.this.AttributeCount, classOf[java.lang.Long])
143 437 6122 - 6171 Block org.geotools.feature.simple.SimpleFeatureTypeBuilder.add sftb.add(UniqueProcess.this.AttributeCount, classOf[java.lang.Long])
146 440 6196 - 6203 Select org.locationtech.geomesa.process.analytic.UniqueProcess.SftName UniqueProcess.this.SftName
146 441 6183 - 6204 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.setName sftb.setName(UniqueProcess.this.SftName)
147 442 6209 - 6230 Apply org.geotools.feature.simple.SimpleFeatureTypeBuilder.buildFeatureType sftb.buildFeatureType()
166 443 6860 - 6892 Apply org.geotools.api.feature.type.AttributeDescriptor.getLocalName AttributeVisitor.this.attributeDescriptor.getLocalName()
167 444 6922 - 6970 Apply scala.collection.mutable.Map.withDefaultValue scala.collection.mutable.Map.empty[Any, Long].withDefaultValue(0L)
169 445 7006 - 7008 Literal <nosymbol> -1
175 446 7263 - 7281 Apply scala.Int.== AttributeVisitor.this.attributeIdx.==(-1)
175 451 7259 - 7259 Literal <nosymbol> ()
175 452 7259 - 7259 Block <nosymbol> ()
176 447 7324 - 7333 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attribute AttributeVisitor.this.attribute
176 448 7306 - 7334 Apply org.geotools.api.feature.simple.SimpleFeatureType.indexOf f.getType().indexOf(AttributeVisitor.this.attribute)
176 449 7291 - 7334 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.attributeIdx_= AttributeVisitor.this.attributeIdx_=(f.getType().indexOf(AttributeVisitor.this.attribute))
176 450 7291 - 7334 Block org.locationtech.geomesa.process.analytic.AttributeVisitor.attributeIdx_= AttributeVisitor.this.attributeIdx_=(f.getType().indexOf(AttributeVisitor.this.attribute))
178 453 7360 - 7372 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attributeIdx AttributeVisitor.this.attributeIdx
178 454 7345 - 7389 TypeApply scala.Any.asInstanceOf f.getAttribute(AttributeVisitor.this.attributeIdx).asInstanceOf[T]
182 455 7470 - 7493 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.getAttribute AttributeVisitor.this.getAttribute[AnyRef](f)
183 456 7502 - 7515 Apply java.lang.Object.!= value.!=(null)
183 460 7498 - 7498 Literal <nosymbol> ()
183 461 7498 - 7498 Block <nosymbol> ()
184 457 7525 - 7549 Apply scala.Long.+ AttributeVisitor.this.uniqueValues.apply(value).+(1)
184 458 7525 - 7549 Apply scala.collection.mutable.MapLike.update AttributeVisitor.this.uniqueValues.update(value, AttributeVisitor.this.uniqueValues.apply(value).+(1))
184 459 7525 - 7549 Block scala.collection.mutable.MapLike.update AttributeVisitor.this.uniqueValues.update(value, AttributeVisitor.this.uniqueValues.apply(value).+(1))
189 462 7634 - 7674 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.getAttribute AttributeVisitor.this.getAttribute[java.util.Collection[_]](f)
190 463 7683 - 7697 Apply java.lang.Object.!= values.!=(null)
190 468 7679 - 7679 Literal <nosymbol> ()
190 469 7679 - 7679 Block <nosymbol> ()
191 464 7730 - 7750 Apply scala.Long.+ AttributeVisitor.this.uniqueValues.apply(x$9).+(1)
191 465 7730 - 7750 Apply scala.collection.mutable.MapLike.update AttributeVisitor.this.uniqueValues.update(x$9, AttributeVisitor.this.uniqueValues.apply(x$9).+(1))
191 466 7707 - 7751 Apply scala.collection.IterableLike.foreach scala.collection.JavaConverters.collectionAsScalaIterableConverter[_$3](values).asScala.foreach[Unit](((x$9: _$3) => AttributeVisitor.this.uniqueValues.update(x$9, AttributeVisitor.this.uniqueValues.apply(x$9).+(1))))
191 467 7707 - 7751 Block scala.collection.IterableLike.foreach scala.collection.JavaConverters.collectionAsScalaIterableConverter[_$3](values).asScala.foreach[Unit](((x$9: _$3) => AttributeVisitor.this.uniqueValues.update(x$9, AttributeVisitor.this.uniqueValues.apply(x$9).+(1))))
196 470 7819 - 7838 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attributeDescriptor AttributeVisitor.this.attributeDescriptor
196 471 7819 - 7845 Select org.locationtech.geomesa.utils.geotools.RichAttributeDescriptors.RichAttributeDescriptor.isList org.locationtech.geomesa.utils.geotools.RichAttributeDescriptors.RichAttributeDescriptor(AttributeVisitor.this.attributeDescriptor).isList
196 472 7849 - 7862 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.addMultiValue AttributeVisitor.this.addMultiValue(f)
196 473 7849 - 7862 Block <nosymbol> { ((f: org.geotools.api.feature.simple.SimpleFeature) => AttributeVisitor.this.addMultiValue(f)) }
196 474 7872 - 7888 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.addSingularValue AttributeVisitor.this.addSingularValue(f)
196 475 7872 - 7888 Block <nosymbol> { ((f: org.geotools.api.feature.simple.SimpleFeature) => AttributeVisitor.this.addSingularValue(f)) }
200 476 7978 - 8013 TypeApply scala.Any.asInstanceOf feature.asInstanceOf[org.geotools.api.feature.simple.SimpleFeature]
201 477 8042 - 8055 Apply org.geotools.api.filter.Filter.evaluate x$10.evaluate(f)
201 478 8022 - 8056 Apply scala.Option.forall AttributeVisitor.this.manualFilter.forall(((x$10: org.geotools.api.filter.Filter) => x$10.evaluate(f)))
201 481 8018 - 8018 Literal <nosymbol> ()
201 482 8018 - 8018 Block <nosymbol> ()
202 479 8066 - 8077 Apply scala.Function1.apply AttributeVisitor.this.addValue.apply(f)
202 480 8066 - 8077 Block scala.Function1.apply AttributeVisitor.this.addValue.apply(f)
206 483 8166 - 8166 TypeApply scala.Predef.$conforms scala.Predef.$conforms[(Any, Long)]
206 484 8153 - 8171 ApplyToImplicitArgs scala.collection.TraversableOnce.toMap AttributeVisitor.this.uniqueValues.toMap[Any, Long](scala.Predef.$conforms[(Any, Long)])
206 485 8133 - 8172 Apply org.locationtech.geomesa.process.analytic.AttributeResult.<init> new AttributeResult(AttributeVisitor.this.uniqueValues.toMap[Any, Long](scala.Predef.$conforms[(Any, Long)]))
215 486 8525 - 8540 Apply org.geotools.api.data.Query.getFilter query.getFilter()
215 487 8512 - 8544 Apply org.locationtech.geomesa.filter.mergeFilters org.locationtech.geomesa.filter.`package`.mergeFilters(query.getFilter(), f)
215 488 8496 - 8545 Apply org.geotools.api.data.Query.setFilter query.setFilter(org.locationtech.geomesa.filter.`package`.mergeFilters(query.getFilter(), f))
215 489 8476 - 8546 Apply scala.Option.foreach AttributeVisitor.this.filter.foreach[Unit](((f: org.geotools.api.filter.Filter) => query.setFilter(org.locationtech.geomesa.filter.`package`.mergeFilters(query.getFilter(), f))))
217 490 8562 - 8578 Apply org.geotools.api.data.FeatureSource.getSchema source.getSchema()
219 491 8605 - 8624 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attributeDescriptor AttributeVisitor.this.attributeDescriptor
219 492 8605 - 8638 Select org.locationtech.geomesa.utils.geotools.RichAttributeDescriptors.RichAttributeDescriptor.isMultiValued org.locationtech.geomesa.utils.geotools.RichAttributeDescriptors.RichAttributeDescriptor(AttributeVisitor.this.attributeDescriptor).isMultiValued
221 493 8688 - 8711 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.uniqueV5 AttributeVisitor.this.uniqueV5(source, query)
221 494 8688 - 8711 Block org.locationtech.geomesa.process.analytic.AttributeVisitor.uniqueV5 AttributeVisitor.this.uniqueV5(source, query)
222 510 8723 - 9403 Block <nosymbol> { query.getHints().put(org.locationtech.geomesa.index.conf.QueryHints.STATS_STRING, org.locationtech.geomesa.utils.stats.Stat.Enumeration(AttributeVisitor.this.attribute)); query.getHints().put(org.locationtech.geomesa.index.conf.QueryHints.ENCODE_STATS, java.lang.Boolean.TRUE); val reader: org.geotools.data.simple.SimpleFeatureIterator = source.getFeatures(query).features(); val enumeration: org.locationtech.geomesa.utils.stats.EnumerationStat[Any] = try { val encoded: String = reader.next().getAttribute(0).asInstanceOf[String]; org.locationtech.geomesa.index.iterators.StatsScan.decodeStat(sft).apply(encoded).asInstanceOf[org.locationtech.geomesa.utils.stats.EnumerationStat[Any]] } finally reader.close(); enumeration.frequencies }
224 495 8825 - 8848 Select org.locationtech.geomesa.index.conf.QueryHints.STATS_STRING org.locationtech.geomesa.index.conf.QueryHints.STATS_STRING
224 496 8867 - 8876 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attribute AttributeVisitor.this.attribute
224 497 8850 - 8877 Apply org.locationtech.geomesa.utils.stats.Stat.Enumeration org.locationtech.geomesa.utils.stats.Stat.Enumeration(AttributeVisitor.this.attribute)
224 498 8806 - 8878 Apply java.awt.RenderingHints.put query.getHints().put(org.locationtech.geomesa.index.conf.QueryHints.STATS_STRING, org.locationtech.geomesa.utils.stats.Stat.Enumeration(AttributeVisitor.this.attribute))
225 499 8904 - 8927 Select org.locationtech.geomesa.index.conf.QueryHints.ENCODE_STATS org.locationtech.geomesa.index.conf.QueryHints.ENCODE_STATS
225 500 8929 - 8951 Select java.lang.Boolean.TRUE java.lang.Boolean.TRUE
225 501 8885 - 8952 Apply java.awt.RenderingHints.put query.getHints().put(org.locationtech.geomesa.index.conf.QueryHints.ENCODE_STATS, java.lang.Boolean.TRUE)
228 502 9000 - 9036 Apply org.geotools.data.simple.SimpleFeatureCollection.features source.getFeatures(query).features()
230 506 9177 - 9317 Block <nosymbol> { val encoded: String = reader.next().getAttribute(0).asInstanceOf[String]; org.locationtech.geomesa.index.iterators.StatsScan.decodeStat(sft).apply(encoded).asInstanceOf[org.locationtech.geomesa.utils.stats.EnumerationStat[Any]] }
232 503 9216 - 9217 Literal <nosymbol> 0
232 504 9191 - 9239 TypeApply scala.Any.asInstanceOf reader.next().getAttribute(0).asInstanceOf[String]
233 505 9248 - 9317 TypeApply scala.Any.asInstanceOf org.locationtech.geomesa.index.iterators.StatsScan.decodeStat(sft).apply(encoded).asInstanceOf[org.locationtech.geomesa.utils.stats.EnumerationStat[Any]]
235 507 9344 - 9358 Apply org.geotools.feature.FeatureIterator.close reader.close()
235 508 9344 - 9358 Block org.geotools.feature.FeatureIterator.close reader.close()
238 509 9374 - 9397 Select org.locationtech.geomesa.utils.stats.EnumerationStat.frequencies enumeration.frequencies
241 511 9409 - 9429 Apply scala.collection.mutable.MapLike.clear AttributeVisitor.this.uniqueValues.clear()
242 512 9470 - 9492 Apply scala.collection.mutable.MapLike.put AttributeVisitor.this.uniqueValues.put(k, v)
242 513 9470 - 9492 Block scala.collection.mutable.MapLike.put AttributeVisitor.this.uniqueValues.put(k, v)
242 514 9434 - 9494 Apply scala.collection.IterableLike.foreach enumerated.foreach[Option[Long]](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((k @ _), (v @ _)) => AttributeVisitor.this.uniqueValues.put(k, v) }))
247 515 9698 - 9707 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attribute AttributeVisitor.this.attribute
247 516 9694 - 9708 Apply scala.collection.generic.GenericCompanion.apply scala.collection.Seq.apply[String](AttributeVisitor.this.attribute)
247 517 9694 - 9715 Select scala.collection.convert.Decorators.AsJava.asJava scala.collection.JavaConverters.seqAsJavaListConverter[String](scala.collection.Seq.apply[String](AttributeVisitor.this.attribute)).asJava
247 518 9671 - 9716 Apply org.geotools.api.data.Query.setPropertyNames query.setPropertyNames(scala.collection.JavaConverters.seqAsJavaListConverter[String](scala.collection.Seq.apply[String](AttributeVisitor.this.attribute)).asJava)
250 519 9832 - 9846 Select org.geotools.api.filter.Filter.INCLUDE org.geotools.api.filter.Filter.INCLUDE
250 520 9873 - 9891 Apply org.geotools.feature.FeatureCollection.getSchema AttributeVisitor.this.features.getSchema()
250 521 9893 - 9902 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attribute AttributeVisitor.this.attribute
250 522 9850 - 9903 Apply org.locationtech.geomesa.index.index.attribute.AttributeIndex.indexed org.locationtech.geomesa.index.index.attribute.AttributeIndex.indexed(AttributeVisitor.this.features.getSchema(), AttributeVisitor.this.attribute)
250 523 9813 - 9903 Apply scala.Boolean.&& query.getFilter().==(org.geotools.api.filter.Filter.INCLUDE).&&(org.locationtech.geomesa.index.index.attribute.AttributeIndex.indexed(AttributeVisitor.this.features.getSchema(), AttributeVisitor.this.attribute))
250 528 9809 - 9809 Literal <nosymbol> ()
250 529 9809 - 9809 Block <nosymbol> ()
251 524 9972 - 9981 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.attribute AttributeVisitor.this.attribute
251 525 9929 - 9982 Apply org.locationtech.geomesa.process.analytic.AttributeVisitor.getIncludeAttributeFilter AttributeVisitor.getIncludeAttributeFilter(AttributeVisitor.this.attribute)
251 526 9913 - 9983 Apply org.geotools.api.data.Query.setFilter query.setFilter(AttributeVisitor.getIncludeAttributeFilter(AttributeVisitor.this.attribute))
251 527 9913 - 9983 Block org.geotools.api.data.Query.setFilter query.setFilter(AttributeVisitor.getIncludeAttributeFilter(AttributeVisitor.this.attribute))
255 530 10040 - 10076 Apply org.geotools.data.simple.SimpleFeatureCollection.features source.getFeatures(query).features()
255 531 10086 - 10094 Select org.locationtech.geomesa.process.analytic.AttributeVisitor.addValue AttributeVisitor.this.addValue
255 532 10020 - 10095 Apply scala.collection.Iterator.foreach org.locationtech.geomesa.utils.collection.SelfClosingIterator.apply(source.getFeatures(query).features()).foreach[Unit](AttributeVisitor.this.addValue)
256 533 10113 - 10113 TypeApply scala.Predef.$conforms scala.Predef.$conforms[(Any, Long)]
256 534 10100 - 10118 ApplyToImplicitArgs scala.collection.TraversableOnce.toMap AttributeVisitor.this.uniqueValues.toMap[Any, Long](scala.Predef.$conforms[(Any, Long)])
271 535 10445 - 10467 Apply org.geotools.api.filter.FilterFactory.property org.locationtech.geomesa.filter.`package`.ff.property(attribute)
271 536 10469 - 10483 Apply org.geotools.api.filter.FilterFactory.literal org.locationtech.geomesa.filter.`package`.ff.literal("")
271 537 10427 - 10484 Apply org.geotools.api.filter.FilterFactory.greaterOrEqual org.locationtech.geomesa.filter.`package`.ff.greaterOrEqual(org.locationtech.geomesa.filter.`package`.ff.property(attribute), org.locationtech.geomesa.filter.`package`.ff.literal(""))
281 538 10711 - 10721 Select org.locationtech.geomesa.process.analytic.AttributeResult.attributes AttributeResult.this.attributes
281 539 10711 - 10728 Select scala.collection.convert.Decorators.AsJava.asJava scala.collection.JavaConverters.mapAsJavaMapConverter[Any, Long](AttributeResult.this.attributes).asJava
284 540 10864 - 10886 Select org.geotools.feature.visitor.CalcResult.NULL_RESULT org.geotools.feature.visitor.CalcResult.NULL_RESULT
284 541 10847 - 10886 Apply java.lang.Object.== targetResults.==(org.geotools.feature.visitor.CalcResult.NULL_RESULT)
284 542 10800 - 10886 Apply scala.Boolean.|| targetResults.isInstanceOf[org.locationtech.geomesa.process.analytic.AttributeResult].||(targetResults.==(org.geotools.feature.visitor.CalcResult.NULL_RESULT))
287 543 10959 - 10986 Select scala.Boolean.unary_! AttributeResult.this.isCompatible(resultsToAdd).unary_!
288 544 10996 - 11068 Throw <nosymbol> throw new scala.`package`.IllegalArgumentException("Parameter is not a compatible type")
288 545 10996 - 11068 Block <nosymbol> throw new scala.`package`.IllegalArgumentException("Parameter is not a compatible type")
289 546 11100 - 11122 Select org.geotools.feature.visitor.CalcResult.NULL_RESULT org.geotools.feature.visitor.CalcResult.NULL_RESULT
289 547 11084 - 11122 Apply java.lang.Object.== resultsToAdd.==(org.geotools.feature.visitor.CalcResult.NULL_RESULT)
289 563 11080 - 11705 If <nosymbol> if (resultsToAdd.==(org.geotools.feature.visitor.CalcResult.NULL_RESULT)) this else if (resultsToAdd.isInstanceOf[org.locationtech.geomesa.process.analytic.AttributeResult]) { val toAdd: Map[Any,Long] = resultsToAdd.getValue().asInstanceOf[Map[Any,Long]]; val merged: scala.collection.immutable.Map[Any,Long] = AttributeResult.this.attributes.++[Long](toAdd.map[(Any, Long), scala.collection.immutable.Map[Any,Long]](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((attr @ _), (count @ _)) => scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L))) }))(immutable.this.Map.canBuildFrom[Any, Long])); new AttributeResult(merged) } else throw new scala.`package`.IllegalArgumentException("The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.")
290 548 11132 - 11136 This org.locationtech.geomesa.process.analytic.AttributeResult this
291 549 11152 - 11194 TypeApply scala.Any.isInstanceOf resultsToAdd.isInstanceOf[org.locationtech.geomesa.process.analytic.AttributeResult]
291 559 11196 - 11540 Block <nosymbol> { val toAdd: Map[Any,Long] = resultsToAdd.getValue().asInstanceOf[Map[Any,Long]]; val merged: scala.collection.immutable.Map[Any,Long] = AttributeResult.this.attributes.++[Long](toAdd.map[(Any, Long), scala.collection.immutable.Map[Any,Long]](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((attr @ _), (count @ _)) => scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L))) }))(immutable.this.Map.canBuildFrom[Any, Long])); new AttributeResult(merged) }
291 562 11148 - 11705 If <nosymbol> if (resultsToAdd.isInstanceOf[org.locationtech.geomesa.process.analytic.AttributeResult]) { val toAdd: Map[Any,Long] = resultsToAdd.getValue().asInstanceOf[Map[Any,Long]]; val merged: scala.collection.immutable.Map[Any,Long] = AttributeResult.this.attributes.++[Long](toAdd.map[(Any, Long), scala.collection.immutable.Map[Any,Long]](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((attr @ _), (count @ _)) => scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L))) }))(immutable.this.Map.canBuildFrom[Any, Long])); new AttributeResult(merged) } else throw new scala.`package`.IllegalArgumentException("The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.")
292 550 11216 - 11266 TypeApply scala.Any.asInstanceOf resultsToAdd.getValue().asInstanceOf[Map[Any,Long]]
294 555 11412 - 11412 TypeApply scala.collection.immutable.Map.canBuildFrom immutable.this.Map.canBuildFrom[Any, Long]
294 556 11402 - 11500 ApplyToImplicitArgs scala.collection.TraversableLike.map toAdd.map[(Any, Long), scala.collection.immutable.Map[Any,Long]](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((attr @ _), (count @ _)) => scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L))) }))(immutable.this.Map.canBuildFrom[Any, Long])
294 557 11388 - 11500 Apply scala.collection.immutable.MapLike.++ AttributeResult.this.attributes.++[Long](toAdd.map[(Any, Long), scala.collection.immutable.Map[Any,Long]](((x0$1: (Any, Long)) => x0$1 match { case (_1: Any, _2: Long)(Any, Long)((attr @ _), (count @ _)) => scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L))) }))(immutable.this.Map.canBuildFrom[Any, Long]))
295 551 11461 - 11491 Apply scala.collection.MapLike.getOrElse AttributeResult.this.attributes.getOrElse[Long](attr, 0L)
295 552 11453 - 11491 Apply scala.Long.+ count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L))
295 553 11444 - 11492 Apply scala.Predef.ArrowAssoc.-> scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L)))
295 554 11444 - 11492 Block scala.Predef.ArrowAssoc.-> scala.Predef.ArrowAssoc[Any](attr).->[Long](count.+(AttributeResult.this.attributes.getOrElse[Long](attr, 0L)))
297 558 11507 - 11534 Apply org.locationtech.geomesa.process.analytic.AttributeResult.<init> new AttributeResult(merged)
299 560 11554 - 11699 Throw <nosymbol> throw new scala.`package`.IllegalArgumentException("The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.")
299 561 11554 - 11699 Block <nosymbol> throw new scala.`package`.IllegalArgumentException("The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.")