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.simple.SimpleFeature
15 import org.geotools.data.collection.ListFeatureCollection
16 import org.geotools.data.simple.SimpleFeatureCollection
17 import org.geotools.process.factory.{DescribeParameter, DescribeProcess, DescribeResult}
18 import org.locationtech.geomesa.features.ScalaSimpleFeature
19 import org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection
20 import org.locationtech.geomesa.index.iterators.StatsScan
21 import org.locationtech.geomesa.index.process.GeoMesaProcessVisitor
22 import org.locationtech.geomesa.index.stats.HasGeoMesaStats
23 import org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor
24 import org.locationtech.geomesa.process.{FeatureResult, GeoMesaProcess}
25 import org.locationtech.geomesa.utils.collection.SelfClosingIterator
26 import org.locationtech.geomesa.utils.geotools.GeometryUtils
27 import org.locationtech.geomesa.utils.stats.Stat
28 
29 @DescribeProcess(
30   title = "Min/Max Process",
31   description = "Gets attribute bounds for a data set"
32 )
33 class MinMaxProcess extends GeoMesaProcess with LazyLogging {
34 
35   @DescribeResult(description = "Output feature collection")
36   def execute(
37                  @DescribeParameter(
38                    name = "features",
39                    description = "The feature set on which to query")
40                  features: SimpleFeatureCollection,
41 
42                  @DescribeParameter(
43                    name = "attribute",
44                    description = "The attribute to gather bounds for")
45                  attribute: String,
46 
47                  @DescribeParameter(
48                    name = "cached",
49                    description = "Return cached values, if available",
50                    min = 0, max = 1)
51                  cached: java.lang.Boolean = null
52 
53              ): SimpleFeatureCollection = {
54 
55     require(attribute != null, "Attribute is a required field")
56 
57     logger.debug(s"Attempting min/max process on type ${features.getClass.getName}")
58 
59     val visitor = new MinMaxVisitor(features, attribute, Option(cached).forall(_.booleanValue()))
60     GeoMesaFeatureCollection.visit(features, visitor)
61     visitor.getResult.results
62   }
63 }
64 
65 object MinMaxProcess {
66 
67   class MinMaxVisitor(features: SimpleFeatureCollection, attribute: String, cached: Boolean)
68       extends GeoMesaProcessVisitor with LazyLogging {
69 
70     private lazy val stat: Stat = Stat(features.getSchema, Stat.MinMax(attribute))
71 
72     private var resultCalc: FeatureResult = _
73 
74     // non-optimized visit
75     override def visit(feature: Feature): Unit = stat.observe(feature.asInstanceOf[SimpleFeature])
76 
77     override def getResult: FeatureResult = {
78       if (resultCalc != null) {
79         resultCalc
80       } else {
81         createResult(stat.toJson)
82       }
83     }
84 
85     override def execute(source: SimpleFeatureSource, query: Query): Unit = {
86       logger.debug(s"Running Geomesa min/max process on source type ${source.getClass.getName}")
87 
88       source.getDataStore match {
89         case ds: HasGeoMesaStats =>
90           resultCalc = ds.stats.getMinMax[Any](source.getSchema, attribute, query.getFilter, !cached) match {
91             case None     => createResult("{}")
92             case Some(mm) => createResult(mm.toJson)
93           }
94 
95         case ds =>
96           logger.warn(s"Running unoptimized min/max query on ${ds.getClass.getName}")
97           SelfClosingIterator(features.features).foreach(visit)
98       }
99     }
100   }
101 
102   private def createResult(stat: String): FeatureResult = {
103     val sf = new ScalaSimpleFeature(StatsScan.StatsSft, "", Array(stat, GeometryUtils.zeroPoint))
104     FeatureResult(new ListFeatureCollection(StatsScan.StatsSft, sf))
105   }
106 }
Line Stmt Id Pos Tree Symbol Tests Code
55 47500 2476 - 2493 Apply java.lang.Object.!= attribute.!=(null)
55 47501 2495 - 2526 Literal <nosymbol> "Attribute is a required field"
55 47502 2468 - 2527 Apply scala.Predef.require scala.Predef.require(attribute.!=(null), "Attribute is a required field")
59 47503 2694 - 2710 Apply java.lang.Boolean.booleanValue x$1.booleanValue()
59 47504 2672 - 2711 Apply scala.Option.forall scala.Option.apply[Boolean](cached).forall(((x$1: Boolean) => x$1.booleanValue()))
59 47505 2633 - 2712 Apply org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.<init> new org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor(features, attribute, scala.Option.apply[Boolean](cached).forall(((x$1: Boolean) => x$1.booleanValue())))
60 47506 2717 - 2766 Apply org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection.visit org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection.visit(features, visitor, org.locationtech.geomesa.index.geotools.GeoMesaFeatureCollection.visit$default$3)
61 47507 2771 - 2796 Select org.locationtech.geomesa.process.FeatureResult.results visitor.getResult().results
75 47508 3197 - 3232 TypeApply scala.Any.asInstanceOf feature.asInstanceOf[org.geotools.api.feature.simple.SimpleFeature]
75 47509 3184 - 3233 Apply org.locationtech.geomesa.utils.stats.Stat.observe MinMaxVisitor.this.stat.observe(feature.asInstanceOf[org.geotools.api.feature.simple.SimpleFeature])
78 47510 3291 - 3309 Apply java.lang.Object.!= MinMaxVisitor.this.resultCalc.!=(null)
79 47511 3321 - 3331 Select org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.resultCalc MinMaxVisitor.this.resultCalc
79 47512 3321 - 3331 Block org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.resultCalc MinMaxVisitor.this.resultCalc
81 47513 3368 - 3379 Select org.locationtech.geomesa.utils.stats.Stat.toJson MinMaxVisitor.this.stat.toJson
81 47514 3355 - 3380 Apply org.locationtech.geomesa.process.analytic.MinMaxProcess.createResult MinMaxProcess.this.createResult(MinMaxVisitor.this.stat.toJson)
81 47515 3355 - 3380 Block org.locationtech.geomesa.process.analytic.MinMaxProcess.createResult MinMaxProcess.this.createResult(MinMaxVisitor.this.stat.toJson)
88 47516 3578 - 3597 Apply org.geotools.api.data.FeatureSource.getDataStore source.getDataStore()
90 47517 3689 - 3705 Apply org.geotools.api.data.FeatureSource.getSchema source.getSchema()
90 47518 3707 - 3716 Select org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.attribute MinMaxVisitor.this.attribute
90 47519 3718 - 3733 Apply org.geotools.api.data.Query.getFilter query.getFilter()
90 47520 3735 - 3742 Select scala.Boolean.unary_! MinMaxVisitor.this.cached.unary_!
90 47521 3665 - 3743 Apply org.locationtech.geomesa.index.stats.GeoMesaStats.getMinMax ds.stats.getMinMax[Any](source.getSchema(), MinMaxVisitor.this.attribute, query.getFilter(), MinMaxVisitor.this.cached.unary_!)
90 47527 3652 - 3864 Apply org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.resultCalc_= MinMaxVisitor.this.resultCalc_=(ds.stats.getMinMax[Any](source.getSchema(), MinMaxVisitor.this.attribute, query.getFilter(), MinMaxVisitor.this.cached.unary_!) match { case scala.None => MinMaxProcess.this.createResult("{}") case (value: org.locationtech.geomesa.utils.stats.MinMax[Any])Some[org.locationtech.geomesa.utils.stats.MinMax[Any]]((mm @ _)) => MinMaxProcess.this.createResult(mm.toJson) })
90 47528 3652 - 3864 Block org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.resultCalc_= MinMaxVisitor.this.resultCalc_=(ds.stats.getMinMax[Any](source.getSchema(), MinMaxVisitor.this.attribute, query.getFilter(), MinMaxVisitor.this.cached.unary_!) match { case scala.None => MinMaxProcess.this.createResult("{}") case (value: org.locationtech.geomesa.utils.stats.MinMax[Any])Some[org.locationtech.geomesa.utils.stats.MinMax[Any]]((mm @ _)) => MinMaxProcess.this.createResult(mm.toJson) })
91 47522 3781 - 3799 Apply org.locationtech.geomesa.process.analytic.MinMaxProcess.createResult MinMaxProcess.this.createResult("{}")
91 47523 3781 - 3799 Block org.locationtech.geomesa.process.analytic.MinMaxProcess.createResult MinMaxProcess.this.createResult("{}")
92 47524 3842 - 3851 Select org.locationtech.geomesa.utils.stats.Stat.toJson mm.toJson
92 47525 3829 - 3852 Apply org.locationtech.geomesa.process.analytic.MinMaxProcess.createResult MinMaxProcess.this.createResult(mm.toJson)
92 47526 3829 - 3852 Block org.locationtech.geomesa.process.analytic.MinMaxProcess.createResult MinMaxProcess.this.createResult(mm.toJson)
95 47532 3882 - 4034 Block <nosymbol> { (if (MinMaxVisitor.this.logger.underlying.isWarnEnabled()) MinMaxVisitor.this.logger.underlying.warn("Running unoptimized min/max query on {}", (ds.getClass().getName(): AnyRef)) else (): Unit); org.locationtech.geomesa.utils.collection.SelfClosingIterator.apply(MinMaxVisitor.this.features.features()).foreach[Unit]({ ((feature: org.geotools.api.feature.Feature) => MinMaxVisitor.this.visit(feature)) }) }
97 47529 4001 - 4018 Apply org.geotools.data.simple.SimpleFeatureCollection.features MinMaxVisitor.this.features.features()
97 47530 4028 - 4033 Apply org.locationtech.geomesa.process.analytic.MinMaxProcess.MinMaxVisitor.visit MinMaxVisitor.this.visit(feature)
97 47531 3981 - 4034 Apply scala.collection.Iterator.foreach org.locationtech.geomesa.utils.collection.SelfClosingIterator.apply(MinMaxVisitor.this.features.features()).foreach[Unit]({ ((feature: org.geotools.api.feature.Feature) => MinMaxVisitor.this.visit(feature)) })
103 47533 4150 - 4168 Select org.locationtech.geomesa.index.iterators.StatsScan.StatsSft org.locationtech.geomesa.index.iterators.StatsScan.StatsSft
103 47534 4170 - 4172 Literal <nosymbol> ""
103 47535 4186 - 4209 Select org.locationtech.geomesa.utils.geotools.GeometryUtils.zeroPoint org.locationtech.geomesa.utils.geotools.GeometryUtils.zeroPoint
103 47536 4174 - 4210 ApplyToImplicitArgs scala.Array.apply scala.Array.apply[AnyRef](stat, org.locationtech.geomesa.utils.geotools.GeometryUtils.zeroPoint)((ClassTag.AnyRef: scala.reflect.ClassTag[AnyRef]))
103 47537 4127 - 4211 Apply org.locationtech.geomesa.features.ScalaSimpleFeature.<init> new org.locationtech.geomesa.features.ScalaSimpleFeature(org.locationtech.geomesa.index.iterators.StatsScan.StatsSft, "", scala.Array.apply[AnyRef](stat, org.locationtech.geomesa.utils.geotools.GeometryUtils.zeroPoint)((ClassTag.AnyRef: scala.reflect.ClassTag[AnyRef])), features.this.ScalaSimpleFeature.<init>$default$4)
104 47538 4256 - 4274 Select org.locationtech.geomesa.index.iterators.StatsScan.StatsSft org.locationtech.geomesa.index.iterators.StatsScan.StatsSft
104 47539 4230 - 4279 Apply org.geotools.data.collection.ListFeatureCollection.<init> new org.geotools.data.collection.ListFeatureCollection(org.locationtech.geomesa.index.iterators.StatsScan.StatsSft, sf)
104 47540 4216 - 4280 Apply org.locationtech.geomesa.process.FeatureResult.apply org.locationtech.geomesa.process.FeatureResult.apply(new org.geotools.data.collection.ListFeatureCollection(org.locationtech.geomesa.index.iterators.StatsScan.StatsSft, sf))