{"action":"created","comment":{"url":"https://api.github.com/repos/locationtech/geomesa/pulls/comments/71233655","id":71233655,"diff_hunk":"@@ -0,0 +1,704 @@\n+package org.locationtech.geomesa.features.avro.complex\r\n+\r\n+import java.nio.ByteBuffer\r\n+import java.sql.Timestamp\r\n+import java.{net, sql, util}\r\n+import java.util.function.Function\r\n+import java.util.{Calendar, Date, GregorianCalendar, UUID, ArrayList => JArrayList, Collection => JCollection, Collections => JCollections, HashMap => JHashMap}\r\n+\r\n+import com.vividsolutions.jts.geom.Geometry\r\n+import com.vividsolutions.jts.io.{WKBReader, WKBWriter}\r\n+import org.apache.avro.Schema\r\n+import org.apache.avro.io.{Decoder, Encoder}\r\n+import org.geotools.feature.`type`.AttributeDescriptorImpl\r\n+import org.geotools.feature.{AttributeImpl, ComplexAttributeImpl, FeatureImpl}\r\n+import org.geotools.filter.identity.FeatureIdImpl\r\n+import org.geotools.gml3.GMLSchema\r\n+import org.locationtech.geomesa.features.avro.complex.AvroSchemas.{FieldNameEncoder, _}\r\n+import org.locationtech.geomesa.utils.geotools.RichAttributeDescriptors._\r\n+import org.opengis.feature.`type`.{PropertyDescriptor, _}\r\n+import org.opengis.feature.{Attribute, ComplexAttribute, Property}\r\n+import org.opengis.filter.identity.FeatureId\r\n+\r\n+import scala.collection.mutable\r\n+import scala.reflect.ClassTag\r\n+\r\n+object AvroCodecs {\r\n+\r\n+  val primitiveTypes =\r\n+    List(\r\n+      classOf[String],\r\n+      classOf[java.lang.Integer],\r\n+      classOf[Int],\r\n+      classOf[java.lang.Long],\r\n+      classOf[Long],\r\n+      classOf[java.lang.Double],\r\n+      classOf[Double],\r\n+      classOf[java.lang.Float],\r\n+      classOf[Float],\r\n+      classOf[java.lang.Boolean],\r\n+      classOf[Boolean]\r\n+    )\r\n+\r\n+  abstract case class AvroCodec[T](schema:Schema) {\r\n+    val clazz: Class[T]\r\n+    def enc(t: T, enc: Encoder): Unit\r\n+    def dec(dec: Decoder): T\r\n+  }\r\n+\r\n+  trait CodecRegistry {\r\n+    protected val registry = mutable.Map.empty[Class[_],mutable.LinkedHashMap[Schema.Type,AvroCodec[_]]]\r\n+    protected val cache = mutable.Map.empty[Class[_], Option[mutable.LinkedHashMap[Schema.Type,AvroCodec[_]]]]\r\n+\r\n+    def bind[T](encMethod: (Encoder,T)=>Unit, decMethod: (Decoder)=>T, schema: Schema)(implicit tag: ClassTag[T]) = {\r\n+      val codec = new AvroCodec[T](schema) {\r\n+        val clazz = tag.runtimeClass.asInstanceOf[Class[T]]\r\n+        override def enc(t: T, out: Encoder): Unit = encMethod(out, t)\r\n+        override def dec(in: Decoder): T = decMethod(in)\r\n+      }\r\n+      registry.getOrElseUpdate(tag.runtimeClass,mutable.LinkedHashMap.empty)(schema.getType) = codec\r\n+      codec\r\n+    }\r\n+\r\n+    val JBOOLEAN = classOf[java.lang.Boolean]\r\n+    val JBYTE = classOf[java.lang.Byte]\r\n+    val JCHAR = classOf[java.lang.Character]\r\n+    val JSHORT = classOf[java.lang.Short]\r\n+    val JINT = classOf[java.lang.Integer]\r\n+    val JLONG = classOf[java.lang.Long]\r\n+    val JFLOAT = classOf[java.lang.Float]\r\n+    val JDOUBLE = classOf[java.lang.Double]\r\n+\r\n+    val normalizePrimitive = Map[Class[_],Class[_]](\r\n+      JBOOLEAN -> classOf[Boolean],\r\n+      JBYTE -> classOf[Byte],\r\n+      JCHAR -> classOf[Char],\r\n+      JSHORT -> classOf[Short],\r\n+      JINT -> classOf[Int],\r\n+      JLONG -> classOf[Long],\r\n+      JFLOAT -> classOf[Float],\r\n+      JDOUBLE -> classOf[Double],\r\n+      java.lang.Boolean.TYPE -> classOf[Boolean],\r\n+      java.lang.Byte.TYPE -> classOf[Byte],\r\n+      java.lang.Character.TYPE -> classOf[Char],\r\n+      java.lang.Short.TYPE -> classOf[Short],\r\n+      java.lang.Integer.TYPE -> classOf[Int],\r\n+      java.lang.Long.TYPE -> classOf[Long],\r\n+      java.lang.Float.TYPE -> classOf[Float],\r\n+      java.lang.Double.TYPE -> classOf[Double]\r\n+    ).withDefault(k=>k)\r\n+\r\n+    def findAll[T](clazz:Class[T]): Option[mutable.LinkedHashMap[Schema.Type, AvroCodec[_]]] =\r\n+      cache.getOrElseUpdate(clazz, registry.get(normalizePrimitive(clazz))\r\n+        .orElse(Option(clazz.getSuperclass).flatMap(findAll(_)))\r\n+        .orElse {\r\n+          for (ifc <- clazz.getInterfaces; map <- findAll(ifc))\r\n+            return Some(map)\r\n+          None\r\n+        }\r\n+      )\r\n+\r\n+    def find[T](clazz:Class[T], s:Schema.Type): Option[AvroCodec[_>:T]] =\r\n+      findAll(clazz).flatMap(m=>m.get(s)).asInstanceOf[Option[AvroCodec[_>:T]]]\r\n+\r\n+    def find[T](clazz:Class[T]): Option[AvroCodec[_>:T]] =\r\n+      findAll(clazz).flatMap(m=>m.headOption).map{case(t,c)=>c}.asInstanceOf[Option[AvroCodec[_>:T]]]\r\n+  }\r\n+\r\n+  trait PrimitiveCodecs extends CodecRegistry {\r\n+    implicit val STRING = bind(_.writeString(_:String), _.readString(), Schema.create(Schema.Type.STRING))\r\n+    implicit val BYTES = bind(_.writeBytes(_:scala.Array[Byte]), d=>{\r\n+      val buf = d.readBytes(null)\r\n+      val pos = buf.position()\r\n+      val len = buf.limit() - pos\r\n+      if(len == 0)\r\n+        scala.Array.emptyByteArray\r\n+      else if(pos == 0 && buf.hasArray)\r\n+        buf.array()\r\n+      else {\r\n+        val a = scala.Array.ofDim[Byte](len)\r\n+        buf.get(a, 0, len)\r\n+        a\r\n+      }\r\n+    }, Schema.create(Schema.Type.BYTES))\r\n+    implicit val BYTEBUFFER = bind(_.writeBytes(_:ByteBuffer), _.readBytes(null), Schema.create(Schema.Type.BYTES))\r\n+    implicit val BOOLEAN = bind(_.writeBoolean(_:Boolean), _.readBoolean(), Schema.create(Schema.Type.BOOLEAN))\r\n+\r\n+    val BYTE_AS_INT = bind[Byte](_.writeInt(_), _.readInt().toByte, Schema.create(Schema.Type.INT))\r\n+    val SHORT_AS_INT = bind[Short](_.writeInt(_), _.readInt().toShort, Schema.create(Schema.Type.INT))\r\n+    implicit val INT = bind[Int](_.writeInt(_), _.readInt(),  Schema.create(Schema.Type.INT))\r\n+    val LONG_AS_INT = bind[Long]((e,l)=>e.writeInt(l.toInt), _.readInt(),  Schema.create(Schema.Type.INT))\r\n+    val FLOAT_AS_INT = bind[Float]((e,l)=>e.writeInt(l.toInt), _.readInt(),  Schema.create(Schema.Type.INT))\r\n+    val DOUBLE_AS_INT = bind[Double]((e,l)=>e.writeInt(l.toInt), _.readInt(),  Schema.create(Schema.Type.INT))\r\n+\r\n+    val BYTE_AS_LONG = bind[Short](_.writeLong(_), _.readLong().toByte,  Schema.create(Schema.Type.LONG))\r\n+    val SHORT_AS_LONG = bind[Short](_.writeLong(_), _.readLong().toShort,  Schema.create(Schema.Type.LONG))\r\n+    val INT_AS_LONG = bind[Int](_.writeLong(_), _.readLong().toInt,  Schema.create(Schema.Type.LONG))\r\n+    implicit val LONG = bind[Long](_.writeLong(_), _.readLong(),  Schema.create(Schema.Type.LONG))\r\n+    val FLOAT_AS_LONG = bind[Float]((e,l)=>e.writeLong(l.toLong), _.readLong(),  Schema.create(Schema.Type.LONG))\r\n+    val DOUBLE_AS_LONG = bind[Double]((e,l)=>e.writeLong(l.toLong), _.readLong(),  Schema.create(Schema.Type.LONG))\r\n+\r\n+    val BYTE_AS_FLOAT = bind[Byte](_.writeFloat(_), _.readFloat().toByte,  Schema.create(Schema.Type.FLOAT))\r\n+    val SHORT_AS_FLOAT = bind[Short](_.writeFloat(_), _.readFloat().toShort,  Schema.create(Schema.Type.FLOAT))\r\n+    val INT_AS_FLOAT = bind[Int](_.writeFloat(_), _.readFloat().toInt,  Schema.create(Schema.Type.FLOAT))\r\n+    val LONG_AS_FLOAT = bind[Long](_.writeFloat(_), _.readFloat().toLong,  Schema.create(Schema.Type.FLOAT))\r\n+    implicit val FLOAT = bind[Float](_.writeFloat(_), _.readFloat(),  Schema.create(Schema.Type.FLOAT))\r\n+    val DOUBLE_AS_FLOAT = bind[Double]((e,f)=>e.writeFloat(f.toFloat), _.readFloat(),  Schema.create(Schema.Type.FLOAT))\r\n+\r\n+    val BYTE_AS_DOUBLE = bind[Byte](_.writeDouble(_), _.readDouble().toByte,  Schema.create(Schema.Type.DOUBLE))\r\n+    val SHORT_AS_DOUBLE = bind[Short](_.writeDouble(_), _.readDouble().toShort,  Schema.create(Schema.Type.DOUBLE))\r\n+    val INT_AS_DOUBLE = bind[Int](_.writeDouble(_), _.readDouble().toInt,  Schema.create(Schema.Type.DOUBLE))\r\n+    val LONG_AS_DOUBLE = bind[Long](_.writeDouble(_), _.readDouble().toLong,  Schema.create(Schema.Type.DOUBLE))\r\n+    val FLOAT_AS_DOUBLE = bind[Float](_.writeDouble(_), _.readDouble().toFloat,  Schema.create(Schema.Type.DOUBLE))\r\n+    implicit val DOUBLE = bind[Double](_.writeDouble(_), _.readDouble(),  Schema.create(Schema.Type.DOUBLE))\r\n+  }\r\n+\r\n+  trait DerivedCodecs extends PrimitiveCodecs {\r\n+    def derive[T,U](encMethod: T=>U, decMethod: U=>T)(implicit primitiveCodec: AvroCodec[U], tag: ClassTag[T]) = {\r\n+      val codec = new AvroCodec[T](primitiveCodec.schema) {\r\n+        override val clazz = tag.runtimeClass.asInstanceOf[Class[T]]\r\n+        override def enc(t: T, out: Encoder) = primitiveCodec.enc(encMethod(t), out)\r\n+        override def dec(in: Decoder) = decMethod(primitiveCodec.dec(in))\r\n+      }\r\n+      registry.getOrElseUpdate(tag.runtimeClass, mutable.LinkedHashMap.empty)(primitiveCodec.schema.getType)=codec\r\n+      codec\r\n+    }\r\n+\r\n+    implicit val DATE = derive[Date,Long](_.getTime, new Date(_))\r\n+    implicit val CALENDAR = derive[Calendar,Long](_.getTimeInMillis, d=>{val gc=new GregorianCalendar(); gc.setTimeInMillis(d); gc})\r\n+    implicit val SQL_DATE = derive[sql.Date,Long](_.getTime, new sql.Date(_))\r\n+    implicit val SQL_TIMESTAMP = derive[Timestamp,Long](_.getTime, new Timestamp(_))\r\n+    implicit val _UUID = derive(encodeUUID, decodeUUID)\r\n+    implicit val URI = derive[net.URI,String](_.toString, net.URI.create)\r\n+  }\r\n+\r\n+  class StatefulCodecs(wkbWriter: WKBWriter, wkbReader: WKBReader) extends DerivedCodecs {\r\n+    val GEOMETRY = derive[Geometry,scala.Array[Byte]](wkbWriter.write, wkbReader.read)\r\n+  }\r\n+\r\n+  def encodeUUID(uuid: UUID) =\r","path":"geomesa-features/geomesa-feature-avro-complex/src/main/scala/org/locationtech/geomesa/features/avro/complex/AvroCodecs.scala","position":179,"original_position":179,"commit_id":"f0026dbd38f2b8aaea703f03a03de11d964e7cba","original_commit_id":"f0026dbd38f2b8aaea703f03a03de11d964e7cba","user":{"login":"jahhulbert-ccri","id":7433591,"avatar_url":"https://avatars.githubusercontent.com/u/7433591?v=3","gravatar_id":"","url":"https://api.github.com/users/jahhulbert-ccri","html_url":"https://github.com/jahhulbert-ccri","followers_url":"https://api.github.com/users/jahhulbert-ccri/followers","following_url":"https://api.github.com/users/jahhulbert-ccri/following{/other_user}","gists_url":"https://api.github.com/users/jahhulbert-ccri/gists{/gist_id}","starred_url":"https://api.github.com/users/jahhulbert-ccri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jahhulbert-ccri/subscriptions","organizations_url":"https://api.github.com/users/jahhulbert-ccri/orgs","repos_url":"https://api.github.com/users/jahhulbert-ccri/repos","events_url":"https://api.github.com/users/jahhulbert-ccri/events{/privacy}","received_events_url":"https://api.github.com/users/jahhulbert-ccri/received_events","type":"User","site_admin":false},"body":"this should be shared with the simple feature code","created_at":"2016-07-18T21:28:57Z","updated_at":"2016-07-18T21:28:57Z","html_url":"https://github.com/locationtech/geomesa/pull/994#discussion_r71233655","pull_request_url":"https://api.github.com/repos/locationtech/geomesa/pulls/994","_links":{"self":{"href":"https://api.github.com/repos/locationtech/geomesa/pulls/comments/71233655"},"html":{"href":"https://github.com/locationtech/geomesa/pull/994#discussion_r71233655"},"pull_request":{"href":"https://api.github.com/repos/locationtech/geomesa/pulls/994"}}},"pull_request":{"url":"https://api.github.com/repos/locationtech/geomesa/pulls/994","id":77552316,"html_url":"https://github.com/locationtech/geomesa/pull/994","diff_url":"https://github.com/locationtech/geomesa/pull/994.diff","patch_url":"https://github.com/locationtech/geomesa/pull/994.patch","issue_url":"https://api.github.com/repos/locationtech/geomesa/issues/994","number":994,"state":"open","locked":false,"title":"Complex avro","user":{"login":"xandris","id":1970616,"avatar_url":"https://avatars.githubusercontent.com/u/1970616?v=3","gravatar_id":"","url":"https://api.github.com/users/xandris","html_url":"https://github.com/xandris","followers_url":"https://api.github.com/users/xandris/followers","following_url":"https://api.github.com/users/xandris/following{/other_user}","gists_url":"https://api.github.com/users/xandris/gists{/gist_id}","starred_url":"https://api.github.com/users/xandris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xandris/subscriptions","organizations_url":"https://api.github.com/users/xandris/orgs","repos_url":"https://api.github.com/users/xandris/repos","events_url":"https://api.github.com/users/xandris/events{/privacy}","received_events_url":"https://api.github.com/users/xandris/received_events","type":"User","site_admin":false},"body":"Rather than try to integrate this into all the existing code myself, I created a separate module that can encode complex features. ComplexEncoderTest isn't a unit test, just a main method. I've just completed all the main functionality so I haven't had a chance to make actual unit tests. Forgive the bad jokes in the example data.\r\n\r\nTo use it, create a new `FeatureCodecs`; this is required to be mixed into a `CodecRegistry` and requires a FieldNameEncoder to be initialized early:\r\n\r\n```\r\n    val reg = new { val fne = FieldNameEncoderV1 } with StatefulCodecs(new WKBWriter(), new WKBReader()) with FeatureCodecs\r\n```\r\n\r\nYou can request an Avro `Schema` from it given a `ComplexType`:\r\n\r\n```\r\n    val schema = reg.schemaFor(carType)\r\n```\r\n\r\nTo actually use this on an Avro `Encoder`, request a codec from the registry:\r\n\r\n```\r\n    val codec = reg.find(car.getDescriptor)\r\n```\r\n\r\nAnd then call `.enc`:\r\n\r\n```\r\n      codec.enc(car, EncoderFactory.get().binaryEncoder(baos, avroenc))\r\n```\r\n\r\nThere is a corresponding `.dec` method, of course.\r\n\r\nThe `FeatureCodecs` mixin attempts to bind an Avro `Schema` to a `ComplexType` once before any encoding or decoding is done; this way, the schemas need only be interrogated when they change and the binding can be reused. There isn't any cache management of these bindings yet; memory is leaked when the schemas change, which could be an issue if they change a lot.\r\n\r\nThe `*Codecs` classes contain mappings between Avro primitives and Java classes. They're used to determine how to generate the `Schema` and how to do the actual encoding and decoding. Multiple Java classes can be bound to the same Avro type, and an Avro type can be bound to many Java classes. When generating a schema, the first Avro type bound to a particular Java type is chosen for the binding. This means that if you define a codec for `Short` that binds to `Schema.Type.INT` but then later define a mapping between `Short` and `Schema.Type.LONG`, the schema generator will prefer `Schema.Type.INT`.\r\n\r\nThe codecs include support for strings, booleans, numeric types, byte arrays, byte buffers, dates, UUIDs, URIs, and geometries.\r\n\r\n`Map` and `List` valued properties are supported as well. I'm not sure if I got the in-memory format right; the Key/Value or List element classes are stored on the `AttributeDescriptor` and retrieved using `RichAttributeDescriptor`. The codecs expect `java.util.List` and `java.util.Map`. Let me know if that's not right. They're encoded directly into Avro and so show up in the schema. The same codecs are used to encode list element and key/value pairs. Maps are encoded as an array of records with `k` and `v` fields.\r\n\r\nProperties inherited from GML AbstractFeatureType are also supported, as well as extensions of anySimpleType with attributes (e.g. gml:description, which has a codespace and an href if you want to store the description elsewhere).\r\n\r\nI tried to profile and improve performance where I could. On my laptop, 500K 'encode' operations followed by 500K 'decode' operations takes 15s, which isn't great but still acceptable. There are a number of un-Scala like bits of code in order to speed things up.\r\n\r\nHow can we integrate this? What's the next step?\r\n\r\nLastly, I am open to changing _anything_. I've been working on this pretty hard this week and my eyes are starting to go a little blurry, so chances are there are mistakes or I've misunderstood something.","created_at":"2016-07-15T01:44:48Z","updated_at":"2016-07-18T21:28:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"960e2556ac7e349e2b500ba8a655330bf2c0915b","assignee":{"login":"jahhulbert-ccri","id":7433591,"avatar_url":"https://avatars.githubusercontent.com/u/7433591?v=3","gravatar_id":"","url":"https://api.github.com/users/jahhulbert-ccri","html_url":"https://github.com/jahhulbert-ccri","followers_url":"https://api.github.com/users/jahhulbert-ccri/followers","following_url":"https://api.github.com/users/jahhulbert-ccri/following{/other_user}","gists_url":"https://api.github.com/users/jahhulbert-ccri/gists{/gist_id}","starred_url":"https://api.github.com/users/jahhulbert-ccri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jahhulbert-ccri/subscriptions","organizations_url":"https://api.github.com/users/jahhulbert-ccri/orgs","repos_url":"https://api.github.com/users/jahhulbert-ccri/repos","events_url":"https://api.github.com/users/jahhulbert-ccri/events{/privacy}","received_events_url":"https://api.github.com/users/jahhulbert-ccri/received_events","type":"User","site_admin":false},"assignees":[{"login":"jahhulbert-ccri","id":7433591,"avatar_url":"https://avatars.githubusercontent.com/u/7433591?v=3","gravatar_id":"","url":"https://api.github.com/users/jahhulbert-ccri","html_url":"https://github.com/jahhulbert-ccri","followers_url":"https://api.github.com/users/jahhulbert-ccri/followers","following_url":"https://api.github.com/users/jahhulbert-ccri/following{/other_user}","gists_url":"https://api.github.com/users/jahhulbert-ccri/gists{/gist_id}","starred_url":"https://api.github.com/users/jahhulbert-ccri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jahhulbert-ccri/subscriptions","organizations_url":"https://api.github.com/users/jahhulbert-ccri/orgs","repos_url":"https://api.github.com/users/jahhulbert-ccri/repos","events_url":"https://api.github.com/users/jahhulbert-ccri/events{/privacy}","received_events_url":"https://api.github.com/users/jahhulbert-ccri/received_events","type":"User","site_admin":false},{"login":"elahrvivaz","id":7715322,"avatar_url":"https://avatars.githubusercontent.com/u/7715322?v=3","gravatar_id":"","url":"https://api.github.com/users/elahrvivaz","html_url":"https://github.com/elahrvivaz","followers_url":"https://api.github.com/users/elahrvivaz/followers","following_url":"https://api.github.com/users/elahrvivaz/following{/other_user}","gists_url":"https://api.github.com/users/elahrvivaz/gists{/gist_id}","starred_url":"https://api.github.com/users/elahrvivaz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elahrvivaz/subscriptions","organizations_url":"https://api.github.com/users/elahrvivaz/orgs","repos_url":"https://api.github.com/users/elahrvivaz/repos","events_url":"https://api.github.com/users/elahrvivaz/events{/privacy}","received_events_url":"https://api.github.com/users/elahrvivaz/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/locationtech/geomesa/pulls/994/commits","review_comments_url":"https://api.github.com/repos/locationtech/geomesa/pulls/994/comments","review_comment_url":"https://api.github.com/repos/locationtech/geomesa/pulls/comments{/number}","comments_url":"https://api.github.com/repos/locationtech/geomesa/issues/994/comments","statuses_url":"https://api.github.com/repos/locationtech/geomesa/statuses/f0026dbd38f2b8aaea703f03a03de11d964e7cba","head":{"label":"xandris:complex-avro","ref":"complex-avro","sha":"f0026dbd38f2b8aaea703f03a03de11d964e7cba","user":{"login":"xandris","id":1970616,"avatar_url":"https://avatars.githubusercontent.com/u/1970616?v=3","gravatar_id":"","url":"https://api.github.com/users/xandris","html_url":"https://github.com/xandris","followers_url":"https://api.github.com/users/xandris/followers","following_url":"https://api.github.com/users/xandris/following{/other_user}","gists_url":"https://api.github.com/users/xandris/gists{/gist_id}","starred_url":"https://api.github.com/users/xandris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xandris/subscriptions","organizations_url":"https://api.github.com/users/xandris/orgs","repos_url":"https://api.github.com/users/xandris/repos","events_url":"https://api.github.com/users/xandris/events{/privacy}","received_events_url":"https://api.github.com/users/xandris/received_events","type":"User","site_admin":false},"repo":{"id":63379267,"name":"geomesa","full_name":"xandris/geomesa","owner":{"login":"xandris","id":1970616,"avatar_url":"https://avatars.githubusercontent.com/u/1970616?v=3","gravatar_id":"","url":"https://api.github.com/users/xandris","html_url":"https://github.com/xandris","followers_url":"https://api.github.com/users/xandris/followers","following_url":"https://api.github.com/users/xandris/following{/other_user}","gists_url":"https://api.github.com/users/xandris/gists{/gist_id}","starred_url":"https://api.github.com/users/xandris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xandris/subscriptions","organizations_url":"https://api.github.com/users/xandris/orgs","repos_url":"https://api.github.com/users/xandris/repos","events_url":"https://api.github.com/users/xandris/events{/privacy}","received_events_url":"https://api.github.com/users/xandris/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/xandris/geomesa","description":"GeoMesa is a suite of tools for working with big geo-spatial data in a distributed fashion.","fork":true,"url":"https://api.github.com/repos/xandris/geomesa","forks_url":"https://api.github.com/repos/xandris/geomesa/forks","keys_url":"https://api.github.com/repos/xandris/geomesa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/xandris/geomesa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/xandris/geomesa/teams","hooks_url":"https://api.github.com/repos/xandris/geomesa/hooks","issue_events_url":"https://api.github.com/repos/xandris/geomesa/issues/events{/number}","events_url":"https://api.github.com/repos/xandris/geomesa/events","assignees_url":"https://api.github.com/repos/xandris/geomesa/assignees{/user}","branches_url":"https://api.github.com/repos/xandris/geomesa/branches{/branch}","tags_url":"https://api.github.com/repos/xandris/geomesa/tags","blobs_url":"https://api.github.com/repos/xandris/geomesa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/xandris/geomesa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/xandris/geomesa/git/refs{/sha}","trees_url":"https://api.github.com/repos/xandris/geomesa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/xandris/geomesa/statuses/{sha}","languages_url":"https://api.github.com/repos/xandris/geomesa/languages","stargazers_url":"https://api.github.com/repos/xandris/geomesa/stargazers","contributors_url":"https://api.github.com/repos/xandris/geomesa/contributors","subscribers_url":"https://api.github.com/repos/xandris/geomesa/subscribers","subscription_url":"https://api.github.com/repos/xandris/geomesa/subscription","commits_url":"https://api.github.com/repos/xandris/geomesa/commits{/sha}","git_commits_url":"https://api.github.com/repos/xandris/geomesa/git/commits{/sha}","comments_url":"https://api.github.com/repos/xandris/geomesa/comments{/number}","issue_comment_url":"https://api.github.com/repos/xandris/geomesa/issues/comments{/number}","contents_url":"https://api.github.com/repos/xandris/geomesa/contents/{+path}","compare_url":"https://api.github.com/repos/xandris/geomesa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/xandris/geomesa/merges","archive_url":"https://api.github.com/repos/xandris/geomesa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/xandris/geomesa/downloads","issues_url":"https://api.github.com/repos/xandris/geomesa/issues{/number}","pulls_url":"https://api.github.com/repos/xandris/geomesa/pulls{/number}","milestones_url":"https://api.github.com/repos/xandris/geomesa/milestones{/number}","notifications_url":"https://api.github.com/repos/xandris/geomesa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/xandris/geomesa/labels{/name}","releases_url":"https://api.github.com/repos/xandris/geomesa/releases{/id}","deployments_url":"https://api.github.com/repos/xandris/geomesa/deployments","created_at":"2016-07-15T00:41:36Z","updated_at":"2016-07-15T00:41:40Z","pushed_at":"2016-07-15T23:32:14Z","git_url":"git://github.com/xandris/geomesa.git","ssh_url":"git@github.com:xandris/geomesa.git","clone_url":"https://github.com/xandris/geomesa.git","svn_url":"https://github.com/xandris/geomesa","homepage":"https://www.locationtech.org/projects/technology.geomesa","size":31986,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"locationtech:master","ref":"master","sha":"00fa3b98fbcd715f4c8b25208b8dcc7ea4e5034a","user":{"login":"locationtech","id":1991411,"avatar_url":"https://avatars.githubusercontent.com/u/1991411?v=3","gravatar_id":"","url":"https://api.github.com/users/locationtech","html_url":"https://github.com/locationtech","followers_url":"https://api.github.com/users/locationtech/followers","following_url":"https://api.github.com/users/locationtech/following{/other_user}","gists_url":"https://api.github.com/users/locationtech/gists{/gist_id}","starred_url":"https://api.github.com/users/locationtech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/locationtech/subscriptions","organizations_url":"https://api.github.com/users/locationtech/orgs","repos_url":"https://api.github.com/users/locationtech/repos","events_url":"https://api.github.com/users/locationtech/events{/privacy}","received_events_url":"https://api.github.com/users/locationtech/received_events","type":"Organization","site_admin":false},"repo":{"id":13226069,"name":"geomesa","full_name":"locationtech/geomesa","owner":{"login":"locationtech","id":1991411,"avatar_url":"https://avatars.githubusercontent.com/u/1991411?v=3","gravatar_id":"","url":"https://api.github.com/users/locationtech","html_url":"https://github.com/locationtech","followers_url":"https://api.github.com/users/locationtech/followers","following_url":"https://api.github.com/users/locationtech/following{/other_user}","gists_url":"https://api.github.com/users/locationtech/gists{/gist_id}","starred_url":"https://api.github.com/users/locationtech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/locationtech/subscriptions","organizations_url":"https://api.github.com/users/locationtech/orgs","repos_url":"https://api.github.com/users/locationtech/repos","events_url":"https://api.github.com/users/locationtech/events{/privacy}","received_events_url":"https://api.github.com/users/locationtech/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/locationtech/geomesa","description":"GeoMesa is a suite of tools for working with big geo-spatial data in a distributed fashion.","fork":false,"url":"https://api.github.com/repos/locationtech/geomesa","forks_url":"https://api.github.com/repos/locationtech/geomesa/forks","keys_url":"https://api.github.com/repos/locationtech/geomesa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/locationtech/geomesa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/locationtech/geomesa/teams","hooks_url":"https://api.github.com/repos/locationtech/geomesa/hooks","issue_events_url":"https://api.github.com/repos/locationtech/geomesa/issues/events{/number}","events_url":"https://api.github.com/repos/locationtech/geomesa/events","assignees_url":"https://api.github.com/repos/locationtech/geomesa/assignees{/user}","branches_url":"https://api.github.com/repos/locationtech/geomesa/branches{/branch}","tags_url":"https://api.github.com/repos/locationtech/geomesa/tags","blobs_url":"https://api.github.com/repos/locationtech/geomesa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/locationtech/geomesa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/locationtech/geomesa/git/refs{/sha}","trees_url":"https://api.github.com/repos/locationtech/geomesa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/locationtech/geomesa/statuses/{sha}","languages_url":"https://api.github.com/repos/locationtech/geomesa/languages","stargazers_url":"https://api.github.com/repos/locationtech/geomesa/stargazers","contributors_url":"https://api.github.com/repos/locationtech/geomesa/contributors","subscribers_url":"https://api.github.com/repos/locationtech/geomesa/subscribers","subscription_url":"https://api.github.com/repos/locationtech/geomesa/subscription","commits_url":"https://api.github.com/repos/locationtech/geomesa/commits{/sha}","git_commits_url":"https://api.github.com/repos/locationtech/geomesa/git/commits{/sha}","comments_url":"https://api.github.com/repos/locationtech/geomesa/comments{/number}","issue_comment_url":"https://api.github.com/repos/locationtech/geomesa/issues/comments{/number}","contents_url":"https://api.github.com/repos/locationtech/geomesa/contents/{+path}","compare_url":"https://api.github.com/repos/locationtech/geomesa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/locationtech/geomesa/merges","archive_url":"https://api.github.com/repos/locationtech/geomesa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/locationtech/geomesa/downloads","issues_url":"https://api.github.com/repos/locationtech/geomesa/issues{/number}","pulls_url":"https://api.github.com/repos/locationtech/geomesa/pulls{/number}","milestones_url":"https://api.github.com/repos/locationtech/geomesa/milestones{/number}","notifications_url":"https://api.github.com/repos/locationtech/geomesa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/locationtech/geomesa/labels{/name}","releases_url":"https://api.github.com/repos/locationtech/geomesa/releases{/id}","deployments_url":"https://api.github.com/repos/locationtech/geomesa/deployments","created_at":"2013-09-30T20:13:46Z","updated_at":"2016-07-15T22:54:59Z","pushed_at":"2016-07-18T20:53:09Z","git_url":"git://github.com/locationtech/geomesa.git","ssh_url":"git@github.com:locationtech/geomesa.git","clone_url":"https://github.com/locationtech/geomesa.git","svn_url":"https://github.com/locationtech/geomesa","homepage":"https://www.locationtech.org/projects/technology.geomesa","size":45281,"stargazers_count":192,"watchers_count":192,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":95,"mirror_url":null,"open_issues_count":9,"forks":95,"open_issues":9,"watchers":192,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/locationtech/geomesa/pulls/994"},"html":{"href":"https://github.com/locationtech/geomesa/pull/994"},"issue":{"href":"https://api.github.com/repos/locationtech/geomesa/issues/994"},"comments":{"href":"https://api.github.com/repos/locationtech/geomesa/issues/994/comments"},"review_comments":{"href":"https://api.github.com/repos/locationtech/geomesa/pulls/994/comments"},"review_comment":{"href":"https://api.github.com/repos/locationtech/geomesa/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/locationtech/geomesa/pulls/994/commits"},"statuses":{"href":"https://api.github.com/repos/locationtech/geomesa/statuses/f0026dbd38f2b8aaea703f03a03de11d964e7cba"}}}}
{"actor":{"display_login":"jahhulbert-ccri"}}