From 159b7348da06fc6dbbace1f526b05a13b1d70be6 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sat, 28 Apr 2012 20:20:32 -0400 Subject: [PATCH 01/53] WIP --- .../src/main/scala/geometry/package.scala | 76 +++++++++++++++ geoscript/src/main/scala/viewer/Viewer.scala | 95 ++++++++++--------- .../test/scala/tutorial/BasicGeometry.scala | 57 +++++++++++ 3 files changed, 182 insertions(+), 46 deletions(-) create mode 100644 geoscript/src/test/scala/tutorial/BasicGeometry.scala diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index 3746efe..936d683 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -6,6 +6,7 @@ package object geometry { type GeometryCollection = jts.GeometryCollection type Polygon = jts.Polygon type MultiPolygon = jts.MultiPolygon + type LinearRing = jts.LinearRing type LineString = jts.LineString type MultiLineString = jts.MultiLineString type Point = jts.Point @@ -14,5 +15,80 @@ package object geometry { type Coordinate = jts.Coordinate type Envelope = jts.Envelope + val EmptyEnvelope = new jts.Envelope + val factory = new jts.GeometryFactory + + def union(a: Envelope, b: Envelope): Envelope = + if (a.isNull) b + else if (b.isNull) a + else { + val res = new jts.Envelope(a) + res.expandToInclude(b) + res + } + + def tupleAsCoordinate(xy: (Double, Double)): Coordinate = + new jts.Coordinate(xy._1, xy._2) + + def coordinate(x: Double, y: Double): Coordinate = + new jts.Coordinate(x, y) + + def point(x: Double, y: Double): Geometry = + factory.createPoint(coordinate(x, y)) + + def lineString(coords: Seq[(Double, Double)]): Geometry = + factory.createLineString(coords.map(tupleAsCoordinate).toArray) + + def linearRing(coords: Seq[(Double, Double)]): LinearRing = + factory.createLinearRing(coords.map(tupleAsCoordinate).toArray) + + def polygon( + shell: Seq[(Double, Double)], + holes: Seq[Seq[(Double, Double)]] = Nil + ): Geometry = + factory.createPolygon( + linearRing(shell), + holes.map(linearRing).toArray) + + def multiPoint(coords: Seq[(Double, Double)]): Geometry = + factory.createMultiPoint( + coords + .map { case (x, y) => factory.createPoint(coordinate(x, y)) } + .toArray + ) + + def multiLineString(strings: Seq[Seq[(Double, Double)]]): Geometry = + factory.createMultiLineString( + strings.map( xs => + factory.createLineString( + xs.map(tupleAsCoordinate).toArray + ) + ).toArray + ) + + def multiPolygon( + polygons: Seq[(Seq[(Double, Double)], Seq[Seq[(Double, Double)]])] + ): Geometry = + factory.createMultiPolygon( + polygons.map { case (shell, holes) => + factory.createPolygon( + linearRing(shell), + holes.map(linearRing).toArray) + }.toArray + ) + + def multi(geoms: Seq[_ <: Geometry]): Geometry = { + if (geoms.forall(_.isInstanceOf[Point])) + factory.createMultiPoint( + geoms.collect { case (p: Point) => p }.toArray) + else if (geoms.forall(_.isInstanceOf[LineString])) + factory.createMultiLineString( + geoms.collect { case (ls: LineString) => ls }.toArray) + else if (geoms.forall(_.isInstanceOf[Polygon])) + factory.createMultiPolygon( + geoms.collect { case (p: Polygon) => p }.toArray) + else + factory.createGeometryCollection(geoms.toArray) + } } diff --git a/geoscript/src/main/scala/viewer/Viewer.scala b/geoscript/src/main/scala/viewer/Viewer.scala index 9cff648..dd8ae7a 100644 --- a/geoscript/src/main/scala/viewer/Viewer.scala +++ b/geoscript/src/main/scala/viewer/Viewer.scala @@ -1,56 +1,59 @@ package org.geoscript -package viewer -import geometry._, layer._, math._, render._, style._ - -import org.geotools.geometry.jts.{ LiteShape, ReferencedEnvelope } -import java.awt.{ Graphics2D, RenderingHints } -import scala.collection.JavaConversions._ - -private class MapWidget extends swing.Component { - var viewport = new ReferencedEnvelope(-180, -90, 180, 90, projection.Projection("EPSG:4326")) - var layers: Seq[SpatialRenderable] = Nil - - override def paint(graphics: swing.Graphics2D) = { - locally { import RenderingHints._ - graphics.setRenderingHints(new RenderingHints(Map( - KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON, - KEY_TEXT_ANTIALIASING -> VALUE_TEXT_ANTIALIAS_ON - ))) - } - import org.geoscript.render.Viewport.pad - val displayBounds = pad(viewport, (bounds.width, bounds.height)) - render(pad(viewport), layers) on Direct(graphics, bounds) +package object viewer { + def showWindow() { + val window = new javax.swing.JFrame + window.setDefaultCloseOperation( + javax.swing.WindowConstants.DISPOSE_ON_CLOSE) + window.setSize(512, 512) + window.setVisible(true) + window.add(geometryComponent) } -} -/** - * The Viewer object provides some rudimentary methods for rendering - * geospatial information on-screen. - */ -object Viewer { - private var window: Option[(swing.Window, MapWidget)] = None + private var visibleGeometries = Seq.empty[geometry.Geometry] - def display(layers: Seq[SpatialRenderable]) { - window match { - case Some((frame, map)) => - map.layers = layers - frame.repaint() - case None => - swing.Swing.onEDT { - val frame = new swing.MainFrame() - val mapViewer = new MapWidget() - mapViewer.layers = layers - // layers.flatMap(_.definitionExtent).reduceOption(_ ** _).foreach { - // mapViewer.viewport = _ - // } - frame.visible = true - frame.contents = mapViewer - frame.size = new swing.Dimension(500, 500) - Viewer.synchronized { - window = Some((frame, mapViewer)) + def worldToScreen( + rect: java.awt.Rectangle, + env: geometry.Envelope + ): java.awt.geom.AffineTransform = { + val xscale = rect.getWidth / env.getWidth + val yscale = rect.getHeight / env.getHeight + val scale = math.min(xscale, yscale) + val deltax = rect.getX - env.getMinX + val deltay = rect.getY - env.getMinY + val tx = new java.awt.geom.AffineTransform() + tx.scale(scale, scale) + tx.translate(deltax, deltay) + tx + } + + val geometryComponent = + new javax.swing.JComponent { + setPreferredSize(new java.awt.Dimension(512, 512)) + + override def paint(graphics: java.awt.Graphics) { + import org.geotools.geometry.jts.LiteShape + import org.geoscript.geometry._ + val envelope = + visibleGeometries.foldLeft(EmptyEnvelope) { + (e: Envelope, g: Geometry) => union(e, g.getEnvelopeInternal) } + val transform = worldToScreen(getBounds(), envelope) + val canvas = graphics.asInstanceOf[java.awt.Graphics2D] + + visibleGeometries.foreach { g => + val shp = new LiteShape(g, transform, true) + if (g.getArea > 0) + canvas.fill(shp) + canvas.draw(shp) } + } + } + + def draw(g: geometry.Geometry) { + synchronized { + visibleGeometries :+= g } + geometryComponent.repaint() } } diff --git a/geoscript/src/test/scala/tutorial/BasicGeometry.scala b/geoscript/src/test/scala/tutorial/BasicGeometry.scala new file mode 100644 index 0000000..39f6951 --- /dev/null +++ b/geoscript/src/test/scala/tutorial/BasicGeometry.scala @@ -0,0 +1,57 @@ +package tutorial + +object BasicGeometry extends App { + import org.geoscript.geometry + // constructing geometries + geometry.point(30, 10) + geometry.lineString(Seq((30, 10), (10, 30), (20, 40), (40, 40))) + geometry.polygon(Seq((30,10), (10,20), (20,40), (40,40), (30,10))) + geometry.polygon( + Seq((35,10), (10,20), (15,40), (45,45), (35,10)), + Seq(Seq((20,30), (35,35), (30,20), (20,30)))) + geometry.multiPoint(Seq((10,40), (40,30), (20,20), (30,10))) + geometry.multi(Seq( + geometry.point(10,40), + geometry.point(40,30), + geometry.point(20,20), + geometry.point(30,10))) + geometry.multiLineString(Seq( + Seq((10,10), (20,20), (10,40)), + Seq((40,40), (30,30), (40,20), (30,10)))) + geometry.multiPolygon(Seq( + (Seq((30,20), (10,40), (45,40), (30,20)), Nil), + (Seq((15,5), (40,10), (10,20), (5,10), (15,5)), Nil))) + + // operations: measurement + val poly = + geometry.polygon(Seq((30, 10), (10, 20), (20, 40), (40, 40), (30, 10))) + poly.getArea + + val line = + geometry.lineString(Seq((30, 10), (10, 20), (20, 40), (40, 40), (30, 10))) + line.getLength + + poly.isValid + + geometry.polygon(Seq((1,1), (2,1), (1,0), (2,0), (1,1))).isValid + + // operations: relationships + val point = geometry.point(30, 10) + point.distance(geometry.point(40, 30)) + + line.distance(point) + line.intersects(point) + line.intersection(point) + + val polyA = geometry.polygon(Seq((0,0), (2,0), (2,2), (0,2), (0,0))) + val polyB = geometry.polygon(Seq((1,1), (3,1), (3,3), (1,3), (1,1))) + polyA.difference(polyB) + polyA.symDifference(polyB) + + // operations: derived geometries + line.buffer(1) + + // (de)serialization + // geometry.WKT.write(point) + // geometry.GeoJSON.write(point) +} From cd8a77adbdb85d75a216a32d64ed0cc7bb5701bf Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 1 May 2012 19:55:28 -0400 Subject: [PATCH 02/53] Redo the render API --- examples/src/main/scala/example/Render.scala | 25 +- .../src/main/scala/render/Viewport.scala | 227 ------------------ geoscript/src/main/scala/render/package.scala | 118 +++++++++ 3 files changed, 127 insertions(+), 243 deletions(-) delete mode 100644 geoscript/src/main/scala/render/Viewport.scala create mode 100644 geoscript/src/main/scala/render/package.scala diff --git a/examples/src/main/scala/example/Render.scala b/examples/src/main/scala/example/Render.scala index 4d2ae1f..0dbf4d4 100644 --- a/examples/src/main/scala/example/Render.scala +++ b/examples/src/main/scala/example/Render.scala @@ -1,22 +1,15 @@ package org.geoscript.example -import org.geoscript.layer.Shapefile, - org.geoscript.style.CSS, - org.geoscript.render.{ render, PNG, Viewport }, - org.geoscript.projection.Projection +object Render extends App { + import org.geoscript.{ layer, style, render, projection }, + render.{ draw, png } -object Render { - def reference(e: org.geoscript.geometry.Envelope, p: Projection) = + def reference(e: org.geoscript.geometry.Envelope, p: projection.Projection) = new org.geotools.geometry.jts.ReferencedEnvelope(e, p) - def main(args: Array[String]) { - val states = Shapefile("geoscript/src/test/resources/data/states.shp") - val theme = CSS.fromFile("geocss/src/test/resources/states.css") - val frame = (1024, 1024) - val viewport = Viewport.pad(reference(states.envelope, Projection("EPSG:4326")), frame) - render( - viewport, - Seq(states -> theme) - ) on PNG("states.png", frame) - } + val states = layer.Shapefile("../geoscript/src/test/resources/data/states.shp") + val theme = style.CSS.fromFile("../geocss/src/test/resources/states.css") + val bounds = reference(states.envelope, projection.Projection("EPSG:4326")) + val win = new org.geoscript.render.Window + draw(states, theme, Some(bounds), (1024, 1024), win) } diff --git a/geoscript/src/main/scala/render/Viewport.scala b/geoscript/src/main/scala/render/Viewport.scala deleted file mode 100644 index 3372e97..0000000 --- a/geoscript/src/main/scala/render/Viewport.scala +++ /dev/null @@ -1,227 +0,0 @@ -package org.geoscript - -import io._ - -import collection.JavaConversions._ -import org.{ geotools => gt } -import com.vividsolutions.jts.{geom=>jts} -import java.awt.{ Graphics2D, Rectangle, RenderingHints } -import geometry.Envelope -import gt.geometry.jts.ReferencedEnvelope - -package render { - trait Context[T] { - def apply(op: (Graphics2D, Rectangle) => Unit): T - } - - case class Direct(graphics: Graphics2D, window: Rectangle) extends Context[Unit] { - def apply(op: (Graphics2D, Rectangle) => Unit): Unit = - op(graphics, window) - } - - case class Raster(area: Rectangle) extends Context[java.awt.image.BufferedImage] { - def apply(draw: (Graphics2D, Rectangle) => Unit): java.awt.image.BufferedImage = { - import java.awt.image.BufferedImage - val image = new BufferedImage( - area.width, - area.height, - BufferedImage.TYPE_INT_RGB - ) - val graphics = image.createGraphics() - draw(graphics, area) - graphics.dispose() - image - } - } - - trait Renderable extends ((Graphics2D, Rectangle) => Unit) { - // hm, this seems a bit gimmicky. - def on[T](context: Context[T]): T = context(this) - } - - object Renderable { - def apply(op: (Graphics2D, Rectangle) => Unit) = - new Renderable { - def apply(graphics: Graphics2D, window: Rectangle) = - op(graphics, window) - } - - def chain(renderables: Seq[Renderable]) = Renderable { (g, w) => - renderables.foreach(_ apply (g, w)) - } - } - - trait SpatialRenderable extends (ReferencedEnvelope => Renderable) { - def definitionExtent: Option[Envelope] - } - - object SpatialRenderable { - implicit def fromGeometry(g: geometry.Geometry) = - new SpatialRenderable { - def definitionExtent: Option[Envelope] = Some(g.envelope) - def apply(bounds: ReferencedEnvelope) = - Renderable { (graphics, window) => - import geometry.Transform - val tx = Transform - .translate(-bounds.getMinX, -bounds.getMinY) - .scale(window.width / bounds.width.toDouble, window.height / bounds.height.toDouble) - .translate(0, -window.height) - .scale(1, -1) - graphics.draw(new org.geotools.geometry.jts.LiteShape(tx(g), null, false)) - } - } - - implicit def fromStyledLayer(t: (layer.Layer, style.Style)) = - new SpatialRenderable { - val (layer, style) = t - def definitionExtent: Option[Envelope] = Some(layer.envelope) - def apply(bounds: ReferencedEnvelope): Renderable = - Renderable { (graphics, window) => - val renderer = new org.geotools.renderer.lite.StreamingRenderer() - locally { import RenderingHints._ - renderer.setJava2DHints(new RenderingHints(Map( - KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON, - KEY_TEXT_ANTIALIASING -> VALUE_TEXT_ANTIALIAS_ON - ))) - } - val content = new org.geotools.map.MapContent - content.layers.add( - new org.geotools.map.FeatureLayer(layer.source, style.underlying) - ) - renderer.setMapContent(content) - renderer.paint(graphics, window, bounds) - content.dispose() - } - } - } - - object Viewport { - /** - * From a list of layers and a desired screen size, determine the size of - * viewing area that preserves the aspect ratio of the layers' bounds and - * fits within the given size. - */ - def frame( - envelope: ReferencedEnvelope, - maximal: (Int, Int) = (500, 500) - ): java.awt.Rectangle = { - val aspect = envelope.height / envelope.width - val idealAspect = maximal._2.toDouble / maximal._1.toDouble - if (aspect < idealAspect) { - new java.awt.Rectangle(0, 0, maximal._1, (maximal._2 * aspect).toInt) - } else { - new java.awt.Rectangle(0, 0, (maximal._1 / aspect).toInt, maximal._2) - } - } - - /** - * From a real-world envelope and a Rectangle representing the display - * area, expand the envelope so that it matches the aspect ratio of the - * desired viewing window. The center of the envelope is preserved. - */ - def pad(envelope: ReferencedEnvelope, window: (Int, Int) = (500, 500)) - : ReferencedEnvelope = { - val aspect = envelope.height / envelope.width - val idealAspect = window._2.toDouble / window._1 - if (aspect < idealAspect) { - val height = envelope.height * (idealAspect/aspect) - new ReferencedEnvelope( - envelope.getMinX, envelope.centre.y - height/2d, - envelope.getMaxX, envelope.centre.y + height/2d, - envelope.getCoordinateReferenceSystem - ) - } else { - val width = envelope.width * (aspect/idealAspect) - new ReferencedEnvelope( - envelope.centre.x - width/2d, envelope.getMinY, - envelope.centre.x + width/2d, envelope.getMaxY, - envelope.getCoordinateReferenceSystem - ) - } - } - } -} - -package object render { - def PNG[T](sink: Sink[T], window: (Int, Int) = (500, 500)): Context[T] = - new Context[T] { - def apply(draw: (Graphics2D, Rectangle) => Unit): T = { - import java.awt.image.BufferedImage - val (width, height) = window - val image = new BufferedImage( - width, height, - BufferedImage.TYPE_INT_ARGB - ) - val graphics = image.createGraphics() - draw(graphics, new Rectangle(0, 0, width, height)) - graphics.dispose() - sink { out => javax.imageio.ImageIO.write(image, "PNG", out) } - } - } - - def JPEG[T](sink: Sink[T], window: Rectangle = new Rectangle(0, 0, 500, 500)): Context[T] = - new Context[T] { - def apply(draw: (Graphics2D, Rectangle) => Unit): T = { - import java.awt.image.BufferedImage - val image = new BufferedImage( - window.width, - window.height, - BufferedImage.TYPE_INT_RGB - ) - val graphics = image.createGraphics() - draw(graphics, window) - graphics.dispose() - sink { out => javax.imageio.ImageIO.write(image, "JPEG", out) } - } - } - - def GIF[T](sink: Sink[T], window: Rectangle = new Rectangle(0, 0, 500, 500)): Context[T] = - new Context[T] { - def apply(draw: (Graphics2D, Rectangle) => Unit): T = { - import java.awt.image.BufferedImage - val image = new BufferedImage( - window.width, - window.height, - BufferedImage.TYPE_INT_RGB - ) - val graphics = image.createGraphics() - draw(graphics, window) - graphics.dispose() - sink { javax.imageio.ImageIO.write(image, "GIF", _) } - } - } - - def PDF[T](sink: Sink[T], - pageDimensions: (Float, Float) = (8.5f * 72, 11f * 72), - dpi: Float = 96f - ): Context[T] = - new Context[T] { - import com.lowagie.{ text => itext } - def apply(draw: (Graphics2D, Rectangle) => Unit): T = { - sink { output => - val (width, height) = pageDimensions - val document = - new itext.Document(new itext.Rectangle(width + 72, height + 72)) - document setMargins(0,0,0,0) // margins in pt, divide by 72 for inches - val writer = itext.pdf.PdfWriter.getInstance(document, output) - val mapper = new itext.pdf.DefaultFontMapper - document.open() - - val content = writer.getDirectContent() - val template = content.createTemplate(width, height) - val graphic = template.createGraphics(width, height, mapper) - - val paintArea = new java.awt.Rectangle(0, 0, width.toInt, height.toInt) - - draw(graphic, paintArea) - graphic.dispose() - content.addTemplate(template, 0, 0) - document.close() - } - } - } - - - def render(bounds: ReferencedEnvelope, layers: Seq[SpatialRenderable]): Renderable = - Renderable.chain(layers.map(_ apply bounds)) -} diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala new file mode 100644 index 0000000..d8da3a7 --- /dev/null +++ b/geoscript/src/main/scala/render/package.scala @@ -0,0 +1,118 @@ +package org.geoscript + +import collection.JavaConverters._ +import org.{ geotools => gt } +import gt.geometry.jts.ReferencedEnvelope +import java.awt +import java.awt.RenderingHints, + RenderingHints.{ KEY_ANTIALIASING, VALUE_ANTIALIAS_ON } + +package render { + trait Stylable[T] { + def applyStyle(t: T, s: style.Style): gt.map.Layer + def boundsOf(t: T): ReferencedEnvelope + } + + object Stylable { + implicit val vectorDataIsStylable: Stylable[layer.Layer] = + new Stylable[layer.Layer] { + override def applyStyle(lyr: layer.Layer, s: style.Style) + : gt.map.Layer = + new gt.map.FeatureLayer(lyr.source, s.underlying, lyr.name) + + override def boundsOf(lyr: layer.Layer): ReferencedEnvelope = + new ReferencedEnvelope(lyr.envelope, lyr.schema.geometry.projection) + } + } + + trait Canvas[T] { self => + def render(size: (Int, Int), draw: awt.Graphics2D => Unit): T + + def map[U](f: T => U): Canvas[U] = new Canvas[U] { + def render(size: (Int, Int), draw: awt.Graphics2D => Unit): U = + f(self.render(size, draw)) + } + } + + class ImageCanvas extends Canvas[awt.image.BufferedImage] { + def render( + size: (Int, Int), draw: awt.Graphics2D => Unit + ): awt.image.BufferedImage = { + val (w, h) = size + val img = new java.awt.image.BufferedImage(w, h, + java.awt.image.BufferedImage.TYPE_INT_ARGB) + + val graphics = img.getGraphics().asInstanceOf[java.awt.Graphics2D] + graphics.setColor(java.awt.Color.WHITE) + graphics.fillRect(0, 0, w, h) + + draw(graphics) + graphics.dispose() + img + } + } + + class Window extends Canvas[javax.swing.JFrame] { + private val component = new java.awt.Canvas { + override def paint(g: awt.Graphics) = + draw(g.asInstanceOf[awt.Graphics2D]) + } + private val frame = new javax.swing.JFrame + frame.setResizable(false) + frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE) + frame.add(component) + + private var draw: awt.Graphics2D => Unit = { _ => } + + def render( + size: (Int, Int), draw: awt.Graphics2D => Unit + ): javax.swing.JFrame = { + frame.setSize(size._1, size._2) + frame.setVisible(true) + this.draw = draw + frame.repaint() + frame + } + } +} + +package object render { + def draw[T, Out]( + t: T, + sty: style.Style, + bounds: Option[ReferencedEnvelope] = None, + size: (Int, Int) = (512, 512), + canvas: Canvas[Out] = new ImageCanvas + ) ( + implicit ev: Stylable[T] + ): Out + = { + val effectiveBounds = bounds.getOrElse(ev.boundsOf(t)) + + val context = new gt.map.DefaultMapContext + context.setAreaOfInterest(effectiveBounds) + context.addLayer(ev.applyStyle(t, sty)) + + val hints = renderHints(KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON) + val renderer = new gt.renderer.lite.StreamingRenderer() + renderer.setJava2DHints(hints) + renderer.setContext(context) + val (w, h) = size + canvas.render(size, { g => + renderer.paint(g, new java.awt.Rectangle(w, h), effectiveBounds) + }) + } + + def file(f: String) = new java.io.File(f) + + def png(f: java.io.File): Canvas[java.io.File] = + new ImageCanvas().map { i => + javax.imageio.ImageIO.write(i, "png", f) + f + } + + def png(f: String): Canvas[java.io.File] = png(file(f)) + + private def renderHints(kv: (RenderingHints.Key, Any)*) = + new RenderingHints(Map(kv: _*).asJava) +} From aa769de4d8a4fd5a7a76d3901ab6a9b1dfa79d9c Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 1 May 2012 20:22:10 -0400 Subject: [PATCH 03/53] Start on styling/rendering tutorial --- .../src/test/scala/tutorial/StylingAndRendering.scala | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 geoscript/src/test/scala/tutorial/StylingAndRendering.scala diff --git a/geoscript/src/test/scala/tutorial/StylingAndRendering.scala b/geoscript/src/test/scala/tutorial/StylingAndRendering.scala new file mode 100644 index 0000000..e5988b5 --- /dev/null +++ b/geoscript/src/test/scala/tutorial/StylingAndRendering.scala @@ -0,0 +1,9 @@ +package test.scala.tutorial +package tutorial + +object StylingAndRendering { + import org.geoscript.style.combinators._ + + val style = Stroke("black", width=2) and Fill("#FF0000", opacity=0.75) + // draw +} From b1bff71abe9363cc4c2cfea69983d0c121d0c259 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 1 May 2012 20:22:26 -0400 Subject: [PATCH 04/53] Make example Render app watch a file and update live --- examples/src/main/scala/example/Render.scala | 30 +++++++++++++++++-- geoscript/src/main/scala/render/package.scala | 5 ++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/examples/src/main/scala/example/Render.scala b/examples/src/main/scala/example/Render.scala index 0dbf4d4..977b257 100644 --- a/examples/src/main/scala/example/Render.scala +++ b/examples/src/main/scala/example/Render.scala @@ -7,9 +7,33 @@ object Render extends App { def reference(e: org.geoscript.geometry.Envelope, p: projection.Projection) = new org.geotools.geometry.jts.ReferencedEnvelope(e, p) - val states = layer.Shapefile("../geoscript/src/test/resources/data/states.shp") - val theme = style.CSS.fromFile("../geocss/src/test/resources/states.css") + val Array(dataFile, styleFile) = + if (args.size > 1) args.take(2) + else Array("../geoscript/src/test/resources/data/states.shp", + "../geocss/src/test/resources/states.css") + + val states = layer.Shapefile(dataFile) + val theme = style.CSS.fromFile(styleFile) val bounds = reference(states.envelope, projection.Projection("EPSG:4326")) val win = new org.geoscript.render.Window - draw(states, theme, Some(bounds), (1024, 1024), win) + draw(states, theme, Some(bounds), (512, 512), win) + + val watcher = new actors.DaemonActor { + val styleFile = new java.io.File("../geocss/src/test/resources/states.css") + var updated = styleFile.lastModified + override def act = loop { + Thread.sleep(1000) + val lastModified = styleFile.lastModified + if (updated < lastModified) { + try { + val theme = style.CSS.fromFile("../geocss/src/test/resources/states.css") + draw(states, theme, Some(bounds), (512, 512), win) + } catch { + case _ => () + } + } + updated = lastModified + } + } + watcher.start() } diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index d8da3a7..d85ea4f 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -54,6 +54,7 @@ package render { class Window extends Canvas[javax.swing.JFrame] { private val component = new java.awt.Canvas { + setBackground(java.awt.Color.WHITE) override def paint(g: awt.Graphics) = draw(g.asInstanceOf[awt.Graphics2D]) } @@ -68,9 +69,9 @@ package render { size: (Int, Int), draw: awt.Graphics2D => Unit ): javax.swing.JFrame = { frame.setSize(size._1, size._2) - frame.setVisible(true) this.draw = draw - frame.repaint() + component.repaint() + if (!frame.isVisible) frame.setVisible(true) frame } } From 9d617cf1b3b0851d40660714db090e2dc62b1066 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:18:35 -0400 Subject: [PATCH 05/53] Remove envelope wrapper class, fix resultant build errors --- geoscript/src/main/scala/Converters.scala | 49 +++--- .../src/main/scala/geometry/Bounds.scala | 153 ------------------ .../src/main/scala/geometry/Geometry.scala | 7 + .../src/main/scala/geometry/package.scala | 3 + geoscript/src/test/scala/GeoHashTest.scala | 2 +- .../scala/tutorial}/GeoHash.scala | 8 +- 6 files changed, 40 insertions(+), 182 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/Bounds.scala rename geoscript/src/{main/scala => test/scala/tutorial}/GeoHash.scala (96%) diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 552a15b..843ca07 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -1,32 +1,31 @@ -package org { - package object geoscript { - import geometry._ +package org +package object geoscript { + import geometry._ - implicit def enrichGeometry(geometry: Geometry): RichGeometry = - new RichGeometry(geometry) + implicit def enrichGeometry(g: Geometry): RichGeometry = + new RichGeometry(g) - implicit def enrichEnvelope(envelope: Envelope): RichEnvelope = - new RichEnvelope(envelope) + implicit def enrichPoint(p: Point): RichPoint = + new RichPoint(p) - implicit def enrichPoint(point: Point): RichPoint = - new RichPoint(point) + implicit def enrichEnvelope(e: Envelope) = + new RichEnvelope(e) - implicit def pointFromPairOfCoordinates[N : Numeric]( - tuple: (N, N) - ): Point = { - val ops = implicitly[Numeric[N]] - Point(ops.toDouble(tuple._1), ops.toDouble(tuple._2)) - } + implicit def pointFromPairOfCoordinates[N : Numeric]( + tuple: (N, N) + ): Point = { + val ops = implicitly[Numeric[N]] + Point(ops.toDouble(tuple._1), ops.toDouble(tuple._2)) + } - implicit def pointFromTripleOfCoordinates[N : Numeric]( - tuple: (N, N, N) - ): Point = { - val ops = implicitly[Numeric[N]] - Point( - ops.toDouble(tuple._1), - ops.toDouble(tuple._2), - ops.toDouble(tuple._3) - ) - } + implicit def pointFromTripleOfCoordinates[N : Numeric]( + tuple: (N, N, N) + ): Point = { + val ops = implicitly[Numeric[N]] + Point( + ops.toDouble(tuple._1), + ops.toDouble(tuple._2), + ops.toDouble(tuple._3) + ) } } diff --git a/geoscript/src/main/scala/geometry/Bounds.scala b/geoscript/src/main/scala/geometry/Bounds.scala deleted file mode 100644 index 186469b..0000000 --- a/geoscript/src/main/scala/geometry/Bounds.scala +++ /dev/null @@ -1,153 +0,0 @@ -package org.geoscript.geometry -import org.geoscript.projection.Projection - -import com.vividsolutions.jts.{ geom => jts } -import org.geotools.geometry.jts.ReferencedEnvelope - -object Envelope { - def apply(minX: Double, minY: Double, maxX: Double, maxY: Double) = - new jts.Envelope(minX, minY, maxX, maxY) - - // import ModuleInternals.factory._ - - // class Wrapper(envelope: jts.Envelope) extends Bounds { - // def minX = envelope.getMinX() - // def maxX = envelope.getMaxX() - // def minY = envelope.getMinY() - // def maxY = envelope.getMaxY() - // def height = envelope.getHeight() - // def width = envelope.getWidth() - - // def shell: LineString = - // LineString( - // Point(minX, minY), - // Point(minX, maxY), - // Point(maxX, maxY), - // Point(maxX, minY), - // Point(minX, minY) - // ) - // def holes: Seq[LineString] = Nil - - // lazy val underlying = - // createPolygon( - // createLinearRing( - // Array( - // new jts.Coordinate(envelope.getMinX(), envelope.getMinY()), - // new jts.Coordinate(envelope.getMinX(), envelope.getMaxY()), - // new jts.Coordinate(envelope.getMaxX(), envelope.getMaxY()), - // new jts.Coordinate(envelope.getMaxX(), envelope.getMinY()), - // new jts.Coordinate(envelope.getMinX(), envelope.getMinY()) - // ) - // ), - // Array() - // ) - // override val bounds = this - // override val prepared = true - // def prepare = this - // def in(proj: Projection) = new Projected(envelope, proj) - // override def transform(dest: Projection) = { - // val lr = Point(envelope.getMinX(), envelope.getMinY()) in projection - // val ul = Point(envelope.getMaxX(), envelope.getMaxY()) in projection - // val projected = new jts.Envelope(lr in dest, ul in dest) - // new Projected(projected, dest) - // } - // } - - // class Projected( - // env: jts.Envelope, - // override val projection: Projection - // ) extends Wrapper(env) { - // override def in(dest: Projection) = transform(dest) - // } - - // def apply(minx: Double, miny: Double, maxx: Double, maxy: Double): Bounds = - // new Wrapper(new jts.Envelope(minx, maxx, miny, maxy)) - - // implicit def apply(env: jts.Envelope): Bounds = - // env match { - // case projected: ReferencedEnvelope - // if projected.getCoordinateReferenceSystem != null - // => - // new Projected(projected, projected.getCoordinateReferenceSystem()) - // case env => - // new Wrapper(env) - // } - - // implicit def unwrap(b: Bounds): ReferencedEnvelope = - // if (b.projection != null) - // new ReferencedEnvelope(b.minX, b.maxX, b.minY, b.maxY, b.projection) - // else - // new ReferencedEnvelope(b.minX, b.maxX, b.minY, b.maxY, null) -} - -// trait Bounds extends Polygon { -// def minX: Double -// def maxX: Double -// def minY: Double -// def maxY: Double -// def height: Double -// def width: Double -// -// def grid(granularity: Int = 4): Iterable[Bounds] = -// (for { -// x <- (0 to granularity).sliding(2) -// y <- (0 to granularity).sliding(2) -// } yield { -// Bounds( -// minX + (x(0) * width / granularity), -// minY + (y(0) * height / granularity), -// minX + (x(1) * width / granularity), -// minY + (y(1) * height / granularity) -// ) in projection -// }) toIterable -// -// def expand(that: Bounds): Bounds = { -// import math.{ min, max } -// val result = -// Bounds( -// min(this.minX, that.minX), min(this.minY, that.minY), -// max(this.maxX, that.maxX), max(this.maxY, that.maxY) -// ) -// if (projection != null) -// result in projection -// else -// result -// } -// -// override def in(dest: Projection): Bounds -// override def transform(dest: Projection): Bounds = null -// override def toString = -// "Bounds((%f, %f), (%f, %f))".format(minX, minY, maxX, maxY) -// } - -class RichEnvelope(envelope: jts.Envelope) { - def minX: Double = envelope.getMinX - def maxX: Double = envelope.getMaxX - def minY: Double = envelope.getMinY - def maxY: Double = envelope.getMaxY - - def height: Double = envelope.getHeight - def width: Double = envelope.getWidth - - def referenced(projection: Projection): ReferencedEnvelope = - new ReferencedEnvelope(minX, maxX, minY, maxY, projection) - - def grid(branching: Int = 4): Iterable[jts.Envelope] = { - val cellHeight = height / branching - val cellWidth = width / branching - for { - i <- (0 until branching) - j <- (0 until branching) - minX = this.minX + i * cellWidth - minY = this.minY + j * cellHeight - maxX = minX + cellWidth - maxY = minY + cellHeight - } yield Envelope(minX, minY, maxX, maxY) - } toIterable - - def ** (that: Envelope): Envelope = { - val clone = new Envelope(envelope) - clone.expandToInclude(that) - clone - } -} diff --git a/geoscript/src/main/scala/geometry/Geometry.scala b/geoscript/src/main/scala/geometry/Geometry.scala index 54fa496..2cff107 100644 --- a/geoscript/src/main/scala/geometry/Geometry.scala +++ b/geoscript/src/main/scala/geometry/Geometry.scala @@ -69,3 +69,10 @@ class RichGeometry(geometry: Geometry) { override def toString = geometry.toString } + +class RichEnvelope(e: Envelope) { + def maxY = e.getMaxY + def maxX = e.getMaxX + def minY = e.getMinY + def minX = e.getMinX +} diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index 936d683..aad5c52 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -33,6 +33,9 @@ package object geometry { def coordinate(x: Double, y: Double): Coordinate = new jts.Coordinate(x, y) + + def envelope(minX: Double, maxX: Double, minY: Double, maxY: Double): Envelope = + new jts.Envelope(minX, maxX, minY, maxY) def point(x: Double, y: Double): Geometry = factory.createPoint(coordinate(x, y)) diff --git a/geoscript/src/test/scala/GeoHashTest.scala b/geoscript/src/test/scala/GeoHashTest.scala index 255debe..df2f2ab 100644 --- a/geoscript/src/test/scala/GeoHashTest.scala +++ b/geoscript/src/test/scala/GeoHashTest.scala @@ -1,7 +1,7 @@ package org.geoscript import org.specs._ -import GeoHash._ +import tutorial.GeoHash._ class GeoHashTest extends Specification { val cases = Seq( diff --git a/geoscript/src/main/scala/GeoHash.scala b/geoscript/src/test/scala/tutorial/GeoHash.scala similarity index 96% rename from geoscript/src/main/scala/GeoHash.scala rename to geoscript/src/test/scala/tutorial/GeoHash.scala index b21cff9..42a34ed 100644 --- a/geoscript/src/main/scala/GeoHash.scala +++ b/geoscript/src/test/scala/tutorial/GeoHash.scala @@ -1,4 +1,6 @@ -package org.geoscript +package tutorial + +import org.geoscript._, geometry._ import Stream._ @@ -16,7 +18,7 @@ object GeoHash { * corresponds to the corners of the geometry's envelope, with a cutoff at 32 * characters to avoid generating infinitely strings for point data. */ - def geohash(geom: geometry.Geometry): String = { + def geohash(geom: Geometry): String = { val bbox = geom.envelope val blHash = geohashForever(bbox.minY, bbox.minX) val urHash = geohashForever(bbox.maxY, bbox.maxX) @@ -62,7 +64,7 @@ object GeoHash { val (minLon, maxLon) = range(lonBits, -180, 180) val (minLat, maxLat) = range(latBits, -90, 90) - geometry.Envelope(minLon, maxLon, minLat, maxLat) + envelope(minLon, maxLon, minLat, maxLat) } /** From 97dc426d872304daf09045033743cd257e0550af Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:23:34 -0400 Subject: [PATCH 06/53] Trim some unused code in Geometry.scala --- .../src/main/scala/geometry/Geometry.scala | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/geoscript/src/main/scala/geometry/Geometry.scala b/geoscript/src/main/scala/geometry/Geometry.scala index 2cff107..c441a5e 100644 --- a/geoscript/src/main/scala/geometry/Geometry.scala +++ b/geoscript/src/main/scala/geometry/Geometry.scala @@ -1,32 +1,6 @@ package org.geoscript.geometry -import com.vividsolutions.jts.{geom=>jts} -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - -/** - * An enumeration of the valid end-cap styles when buffering a (line) Geometry. - * Valid styles include: - *
    - *
  • Round - A semicircle
  • - *
  • Butt - A straight line perpendicular to the end segment
  • - *
  • Square - A half-square
  • - *
- * - * @see org.geoscript.geometry.Geometry.buffer - */ -object EndCap { - // import com.vividsolutions.jts.operation.buffer.BufferOp._ - import com.vividsolutions.jts.operation.buffer.BufferParameters._ - - sealed abstract class Style { val intValue: Int } - /** @see EndCap */ - case object Butt extends Style { val intValue = CAP_FLAT } - /** @see EndCap */ - case object Round extends Style { val intValue = CAP_ROUND } - /** @see EndCap */ - case object Square extends Style { val intValue = CAP_SQUARE } -} +import com.vividsolutions.jts.{ geom => jts } class RichGeometry(geometry: Geometry) { /** @@ -58,15 +32,6 @@ class RichGeometry(geometry: Geometry) { */ def length: Double = geometry.getLength() - def mapVertices(op: Point => Point): Geometry = { - val geom = geometry.clone().asInstanceOf[jts.Geometry] - object filter extends jts.CoordinateFilter { - def filter(coord: jts.Coordinate) = op(Point(coord)).getCoordinate - } - geom.apply(filter) - geom - } - override def toString = geometry.toString } From ab3aaf3af62c8270f5f87a611dd4ef2d3681d4dc Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:26:42 -0400 Subject: [PATCH 07/53] Get rid of GeometryCollection companion --- .../scala/geometry/GeometryCollection.scala | 28 ------------------- .../geometry/SerializationSpec.scala | 8 +++--- 2 files changed, 4 insertions(+), 32 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/GeometryCollection.scala diff --git a/geoscript/src/main/scala/geometry/GeometryCollection.scala b/geoscript/src/main/scala/geometry/GeometryCollection.scala deleted file mode 100644 index 9bd0f7a..0000000 --- a/geoscript/src/main/scala/geometry/GeometryCollection.scala +++ /dev/null @@ -1,28 +0,0 @@ -package org.geoscript.geometry - -import org.geoscript.projection.Projection -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import com.vividsolutions.jts.{geom => jts} - -/** - * A companion object for the GeometryCollection type, providing various - * methods for directly instantiating GeometryCollection objects. - */ -object GeometryCollection { - def apply(geoms: Geometry*): GeometryCollection = - factory.createGeometryCollection(geoms.toArray) -} - -// /** -// * A GeometryCollection aggregates 0 or more Geometry objects together and -// * allows spatial calculations to be performed against the collection as if it -// * were a single geometry. For example, the area of the collection is simply -// * the sum of the areas of its constituent geometry objects. -// */ -// trait GeometryCollection extends Geometry { -// def members: Seq[Geometry] -// override val underlying: jts.GeometryCollection -// override def in(proj: Projection): GeometryCollection -// override def transform(dest: Projection): GeometryCollection = -// GeometryCollection(projection.to(dest)(underlying)) in dest -// } diff --git a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index 4aa5912..5fc2c1b 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -73,10 +73,10 @@ class SerializationSpec extends Specification { } "round-trip a GeometryCollection" in { - val gc = GeometryCollection( - Point(100.0, 0.0), - LineString((101.0, 0.0), (102.0, 1.0)) - ) + val gc = multi(Seq( + point(100.0, 0.0), + lineString(Seq((101.0, 0.0), (102.0, 1.0))) + )) io.GeoJSON.write(gc, Sink.string) must_== """{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[100,0.0]},{"type":"LineString","coordinates":[[101,0.0],[102,1]]}]}""" From 165a6b873c87eb5a52a5447b07c0d0be64833e32 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:27:15 -0400 Subject: [PATCH 08/53] Remove empty Implicits.scala --- .../src/main/scala/geometry/Implicits.scala | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/Implicits.scala diff --git a/geoscript/src/main/scala/geometry/Implicits.scala b/geoscript/src/main/scala/geometry/Implicits.scala deleted file mode 100644 index ace8eae..0000000 --- a/geoscript/src/main/scala/geometry/Implicits.scala +++ /dev/null @@ -1,26 +0,0 @@ -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} - -/** - * A number of implicit conversions for dealing with geometries. GeoCrunch - * users may prefer to simply extend org.geoscript.GeoScript which - * collects implicits for many GeoScript objects. - */ -// trait Implicits { -// implicit def tuple2coord(t: (Number, Number)): jts.Coordinate = -// new jts.Coordinate(t._1.doubleValue(), t._2.doubleValue()) -// -// implicit def tuple2coord(t: (Number, Number, Number)): jts.Coordinate = -// new jts.Coordinate( -// t._1.doubleValue(), t._2.doubleValue(), t._3.doubleValue() -// ) -// -// implicit def point2coord(p: jts.Point): jts.Coordinate = p.getCoordinate() -// -// implicit def enrichPoint(p: jts.Point): Point = Point(p) -// -// implicit def wrapGeom(geom: jts.Geometry): Geometry = Geometry(geom) -// -// implicit def unwrapGeom(geom: Geometry): jts.Geometry = geom.underlying -// } From 20f4014a9d18e3e5cbb05172fb68059c3d6996ba Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:34:56 -0400 Subject: [PATCH 09/53] Remove LineString companion object --- .../src/main/scala/geometry/LineString.scala | 33 ------------------- geoscript/src/test/scala/UsageTests.scala | 10 +++--- .../geometry/SerializationSpec.scala | 32 +++++++++--------- 3 files changed, 21 insertions(+), 54 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/LineString.scala diff --git a/geoscript/src/main/scala/geometry/LineString.scala b/geoscript/src/main/scala/geometry/LineString.scala deleted file mode 100644 index 54300d1..0000000 --- a/geoscript/src/main/scala/geometry/LineString.scala +++ /dev/null @@ -1,33 +0,0 @@ -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - -/** - * A companion object for the LineString type, providing various - * methods for directly instantiating LineString objects. - */ -object LineString { - /** - * Create a LineString from JTS Coordinates. - */ - def apply(coords: Point*): LineString = - factory.createLineString( - (coords map (_.getCoordinate))(collection.breakOut): Array[Coordinate] - ) -} - -// /** -// * A LineString contains 0 or more contiguous line segments, and is useful for -// * representing geometries such as roads or rivers. -// */ -// trait LineString extends Geometry { -// def vertices: Seq[Point] -// override val underlying: jts.LineString -// def isClosed: Boolean = underlying.isClosed -// override def in(dest: Projection): LineString -// override def transform(dest: Projection): LineString = -// LineString(projection.to(dest)(underlying)) in dest -// } diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 6af902d..5a26205 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -18,17 +18,17 @@ class UsageTests extends Specification { } "linestrings should be easy" in { - LineString( + lineString(Seq( (10.0, 10.0), (20.0, 20.0), (30.0, 40.0) - ).length must beCloseTo(36.503, 0.001) + )).length must beCloseTo(36.503, 0.001) - LineString((10, 10), (20.0, 20.0), (30, 40)) + lineString(Seq((10, 10), (20.0, 20.0), (30, 40))) .length must beCloseTo(36.503, 0.001) } "polygon should be easy" in { - Polygon( - LineString((10, 10), (10, 20), (20, 20), (20, 15), (10, 10)) + polygon( + Seq((10, 10), (10, 20), (20, 20), (20, 15), (10, 10)) ).area must_== 75 } diff --git a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index 5fc2c1b..d1266ce 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -15,19 +15,19 @@ class SerializationSpec extends Specification { } "round-trip linestrings" in { - val ls = LineString((100, 0), (101, 1)) + val ls = lineString(Seq((100, 0), (101, 1))) io.GeoJSON.write(ls, Sink.string) must_== """{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""" } "round-trip polygons" in { - val solid = Polygon( - LineString((100, 0), (101, 0), (101, 1), (100, 1), (100, 0)) + val solid = polygon( + Seq((100, 0), (101, 0), (101, 1), (100, 1), (100, 0)) ) - val withHoles = Polygon( - LineString((100, 0), (101, 0), (101, 1), (100, 1), (100, 0)), - Seq(LineString( + val withHoles = polygon( + Seq((100, 0), (101, 0), (101, 1), (100, 1), (100, 0)), + Seq(Seq( (100.2, 0.2), (100.8, 0.2), (100.8, 0.8), (100.2, 0.8), (100.2, 0.2) )) ) @@ -45,28 +45,28 @@ class SerializationSpec extends Specification { } "round-trip a MultiLineString" in { - val mls = MultiLineString( - LineString((100, 0), Point(101, 1)), - LineString((102, 2), Point(103, 3)) - ) + val mls = multiLineString(Seq( + Seq((100, 0), (101, 1)), + Seq((102, 2), (103, 3)) + )) io.GeoJSON.write(mls, Sink.string) must_== """{"type":"MultiLineString","coordinates":[[[100,0.0],[101,1]],[[102,2],[103,3]]]}""" } "round-trip a MultiPolygon" in { - val mp = MultiPolygon( - Polygon(LineString( + val mp = multiPolygon(Seq( + (Seq( (102, 2), (103, 2), (103, 3), (102, 3), (102, 2) - )), - Polygon(LineString( + ), Nil), + (Seq( (100, 0), (101, 0), (101, 1), (100, 1), (100, 0) ), - Seq(LineString( + Seq(Seq( (100.2, 0.2), (100.8, 0.2), (100.8, 0.8), (100.2, 0.8), (100.2, 0.2) )) ) - ) + )) io.GeoJSON.write(mp, Sink.string) must_== """{"type":"MultiPolygon","coordinates":[[[[102,2],[103,2],[103,3],[102,3],[102,2]]],[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]]}""" From b9698cba3b9620a0954e22e14f776c8f557ddabd Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:36:43 -0400 Subject: [PATCH 10/53] Remove MultiLineString companion object --- .../main/scala/geometry/MultiLineString.scala | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/MultiLineString.scala diff --git a/geoscript/src/main/scala/geometry/MultiLineString.scala b/geoscript/src/main/scala/geometry/MultiLineString.scala deleted file mode 100644 index 0582a01..0000000 --- a/geoscript/src/main/scala/geometry/MultiLineString.scala +++ /dev/null @@ -1,35 +0,0 @@ -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - - -/** - * A companion object for the MultiLineString type, providing various - * methods for directly instantiating MultiLineString objects. - */ -object MultiLineString { - /** - * Create a MultiLineString from a list of JTS LineStrings - */ - def apply(lines: Iterable[LineString]): MultiLineString = - factory.createMultiLineString(lines.toArray) - - def apply(lines: LineString*): MultiLineString = - factory.createMultiLineString(lines.toArray) -} - -// /** -// * A MultiLineString aggregates 0 or more line strings and allows them to be -// * treated as a single geometry. For example, the length of a multilinestring -// * is the sum of the length of its constituent linestrings. -// */ -// trait MultiLineString extends Geometry { -// def members: Seq[LineString] -// override val underlying: jts.MultiLineString -// override def in(dest: Projection): MultiLineString -// override def transform(dest: Projection): MultiLineString = -// MultiLineString(projection.to(dest)(underlying)) in dest -// } From 638ead8adf0f6db72f9638ed0913634774f9f0a0 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:39:01 -0400 Subject: [PATCH 11/53] Remove MultiPoint companion object --- .../src/main/scala/geometry/MultiPoint.scala | 19 ------------------- geoscript/src/test/scala/UsageTests.scala | 2 +- .../geometry/SerializationSpec.scala | 2 +- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/MultiPoint.scala diff --git a/geoscript/src/main/scala/geometry/MultiPoint.scala b/geoscript/src/main/scala/geometry/MultiPoint.scala deleted file mode 100644 index 9874a1d..0000000 --- a/geoscript/src/main/scala/geometry/MultiPoint.scala +++ /dev/null @@ -1,19 +0,0 @@ -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - -/** - * A companion object for the MultiPoint type, providing various methods for - * directly instantiating MultiPoint objects. - */ -object MultiPoint { - /** - * Create a MultiPoint from a list of input objects. These objects can be - * Points, JTS Points, JTS Coordinates, or tuples of numeric types. - */ - def apply(coords: Point*): MultiPoint = - factory.createMultiPoint(coords.toArray) -} diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 5a26205..5add6a8 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -33,7 +33,7 @@ class UsageTests extends Specification { } "multi point should be easy" in { - MultiPoint((20, 20), (10.0, 10.0)).area must_== 0 + multiPoint(Seq((20, 20), (10.0, 10.0))).area must_== 0 } } diff --git a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index d1266ce..987752b 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -39,7 +39,7 @@ class SerializationSpec extends Specification { } "round-trip a multipoint" in { - val mp = MultiPoint((100.0, 0.0), (101.0, 1.0)) + val mp = multiPoint(Seq((100.0, 0.0), (101.0, 1.0))) io.GeoJSON.write(mp, Sink.string) must_== """{"type":"MultiPoint","coordinates":[[100,0.0],[101,1]]}""" } From 2ea9381be032f9c2cfb80d172fce6d6709b2bee9 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:39:46 -0400 Subject: [PATCH 12/53] Remove MultiPolygon companion object --- .../main/scala/geometry/MultiPolygon.scala | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/MultiPolygon.scala diff --git a/geoscript/src/main/scala/geometry/MultiPolygon.scala b/geoscript/src/main/scala/geometry/MultiPolygon.scala deleted file mode 100644 index 811630b..0000000 --- a/geoscript/src/main/scala/geometry/MultiPolygon.scala +++ /dev/null @@ -1,31 +0,0 @@ - -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - -/** - * A companion object for the MultiPolygon type, providing various - * methods for directly instantiating MultiPolygon objects. - */ -object MultiPolygon { - def apply(polygons: Iterable[Polygon]): MultiPolygon = - factory.createMultiPolygon(polygons toArray) - - def apply(polygons: Polygon*): MultiPolygon = - factory.createMultiPolygon(polygons toArray) -} - -// /** -// * A MultiPolygon is a collection of 0 or more polygons that can be treated as -// * a single geometry. -// */ -// trait MultiPolygon extends Geometry { -// def members: Seq[Polygon] -// override val underlying: jts.MultiPolygon -// override def in(dest: Projection): MultiPolygon -// override def transform(dest: Projection): MultiPolygon = -// MultiPolygon(projection.to(dest)(underlying)) in dest -// } From a1bd1c333c81fcd737df912febde890a72f1d8a3 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:45:42 -0400 Subject: [PATCH 13/53] Remove Point companion object --- geoscript/src/main/scala/Converters.scala | 18 ----------- .../src/main/scala/geometry/Geometry.scala | 8 +++-- geoscript/src/main/scala/geometry/Point.scala | 32 ------------------- .../src/main/scala/geometry/package.scala | 2 +- geoscript/src/test/scala/UsageTests.scala | 6 ++-- .../geometry/SerializationSpec.scala | 2 +- .../org/geoscript/workspaces/MemorySpec.scala | 5 ++- 7 files changed, 13 insertions(+), 60 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/Point.scala diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 843ca07..0ec4689 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -10,22 +10,4 @@ package object geoscript { implicit def enrichEnvelope(e: Envelope) = new RichEnvelope(e) - - implicit def pointFromPairOfCoordinates[N : Numeric]( - tuple: (N, N) - ): Point = { - val ops = implicitly[Numeric[N]] - Point(ops.toDouble(tuple._1), ops.toDouble(tuple._2)) - } - - implicit def pointFromTripleOfCoordinates[N : Numeric]( - tuple: (N, N, N) - ): Point = { - val ops = implicitly[Numeric[N]] - Point( - ops.toDouble(tuple._1), - ops.toDouble(tuple._2), - ops.toDouble(tuple._3) - ) - } } diff --git a/geoscript/src/main/scala/geometry/Geometry.scala b/geoscript/src/main/scala/geometry/Geometry.scala index c441a5e..c77152f 100644 --- a/geoscript/src/main/scala/geometry/Geometry.scala +++ b/geoscript/src/main/scala/geometry/Geometry.scala @@ -23,8 +23,7 @@ class RichGeometry(geometry: Geometry) { /** * All the coordinates that compose this Geometry as a sequence. */ - def coordinates: Seq[Point] = - geometry.getCoordinates() map (c => Point(c)) // in projection) + def coordinates: Seq[Coordinate] = geometry.getCoordinates().toSeq /** * The length of the line segments that compose this geometry, in the same @@ -41,3 +40,8 @@ class RichEnvelope(e: Envelope) { def minY = e.getMinY def minX = e.getMinX } + +class RichPoint(point: Point) extends RichGeometry(point) { + def x = point.getX + def y = point.getY +} diff --git a/geoscript/src/main/scala/geometry/Point.scala b/geoscript/src/main/scala/geometry/Point.scala deleted file mode 100644 index ccb8ee7..0000000 --- a/geoscript/src/main/scala/geometry/Point.scala +++ /dev/null @@ -1,32 +0,0 @@ -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - -/** - * A companion object for the Point type, providing various - * methods for directly instantiating Point objects. - */ -object Point extends { - - def apply(coordinate: Coordinate) = factory.createPoint(coordinate) - - /** - * Create a 3-dimensional point directly from coordinates. - */ - def apply(x: Double, y: Double, z: Double): Point = - factory.createPoint(new jts.Coordinate(x, y, z)) - - /** - * Create a 2-dimensional point directly from coordinates. - */ - def apply(x: Double, y: Double): Point = - factory.createPoint(new jts.Coordinate(x, y)) -} - -class RichPoint(point: Point) extends RichGeometry(point) { - def x = point.getX - def y = point.getY -} diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index aad5c52..9f029f3 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -37,7 +37,7 @@ package object geometry { def envelope(minX: Double, maxX: Double, minY: Double, maxY: Double): Envelope = new jts.Envelope(minX, maxX, minY, maxY) - def point(x: Double, y: Double): Geometry = + def point(x: Double, y: Double): Point = factory.createPoint(coordinate(x, y)) def lineString(coords: Seq[(Double, Double)]): Geometry = diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 5add6a8..e5276e7 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -8,7 +8,7 @@ import projection._ class UsageTests extends Specification { "geometries" should { "work like on the geoscript homepage" in { - var p = Point(-111, 45.7) + var p = point(-111, 45.7) var p2 = (Projection("epsg:4326") to Projection("epsg:26912"))(p) var poly = p.buffer(100) @@ -90,12 +90,12 @@ class UsageTests extends Specification { dummy += feature.Feature( "name" -> "San Francisco", - "geom" -> Point(37.78, -122.42) + "geom" -> point(37.78, -122.42) ) dummy += feature.Feature( "name" -> "New York", - "geom" -> Point(40.47, -73.58) + "geom" -> point(40.47, -73.58) ) dummy.count must_== 2 diff --git a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index 987752b..9ab2b96 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -7,7 +7,7 @@ import org.specs._ class SerializationSpec extends Specification { "JSON Serialization" should { "round-trip points" in { - val p = Point(100, 0) + val p = point(100, 0) val json = io.GeoJSON.write(p, Sink.string) json must_== """{"type":"Point","coordinates":[100,0.0]}""" // io.GeoJSON.read(Source.string(json)) must_== p diff --git a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index 0f0a0ae..770b867 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -2,19 +2,18 @@ package org.geoscript package workspace import org.specs._ -import com.vividsolutions.jts.geom.Point class MemorySpec extends Specification { "Memory datastores" should { "be able to create layers" in { val schema = feature.Schema("cities", - feature.Field("the_geom", classOf[Point], "EPSG:4326"), + feature.Field("the_geom", classOf[geometry.Point], "EPSG:4326"), feature.Field("name", classOf[String]) ) val ws = workspace.Memory() val lyr = ws.create(schema) lyr += feature.Feature( - "the_geom" -> geometry.Point(0, 0), + "the_geom" -> geometry.point(0, 0), "name" -> "test" ) lyr.envelope must not(throwAn[Exception]) From b508b266a25028bd9018711d0f970aef8c02cc5b Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 6 May 2012 22:46:35 -0400 Subject: [PATCH 14/53] Remove Polygon companion object --- .../src/main/scala/geometry/Polygon.scala | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 geoscript/src/main/scala/geometry/Polygon.scala diff --git a/geoscript/src/main/scala/geometry/Polygon.scala b/geoscript/src/main/scala/geometry/Polygon.scala deleted file mode 100644 index 0a8fc50..0000000 --- a/geoscript/src/main/scala/geometry/Polygon.scala +++ /dev/null @@ -1,36 +0,0 @@ -package org.geoscript.geometry - -import com.vividsolutions.jts.{geom=>jts} -import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory -import org.opengis.referencing.crs.CoordinateReferenceSystem -import org.geoscript.projection.Projection - -/** - * A companion object for the Polygon type, providing various methods for - * directly instantiating Polygon objects. - */ -object Polygon { - /** - * Create a Polygon from an outer shell and a list of zero or more holes. - */ - def apply(shell: LineString, holes: Seq[LineString] = Nil): Polygon = - factory.createPolygon( - factory.createLinearRing(shell.getCoordinateSequence()), - holes.map(hole => - factory.createLinearRing(hole.getCoordinateSequence()) - )(collection.breakOut) - ) -} - -// /** -// * A polygon represents a contiguous area, possibly with holes. -// */ -// trait Polygon extends Geometry { -// def shell: LineString -// def holes: Seq[LineString] -// def rings: Seq[LineString] = Seq(shell) ++ holes -// override val underlying: jts.Polygon -// override def in(dest: Projection): Polygon -// override def transform(dest: Projection): Polygon = -// Polygon(projection.to(dest)(underlying)) in dest -// } From 169bde26b1fb94c51408db79af67fd897a97d569 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 13 May 2012 20:43:53 -0400 Subject: [PATCH 15/53] Revise layer package API Big new changes here: * Use type alias with implicit conversion, instead of wrapper class * Use loan pattern instead of trying to integrate featurecollections into Scala collections framework --- .../src/main/scala/example/AllValid.scala | 4 +- .../src/main/scala/example/ColorRamp.scala | 6 +- .../src/main/scala/example/FirstProject.scala | 4 +- .../main/scala/example/Intersections.scala | 24 +-- examples/src/main/scala/example/Postgis.scala | 2 +- examples/src/main/scala/example/Render.scala | 3 +- examples/src/main/scala/example/Shp2Shp.scala | 4 +- geoscript/src/main/scala/Converters.scala | 4 + .../src/main/scala/feature/Feature.scala | 6 + geoscript/src/main/scala/layer/Layer.scala | 153 ------------------ geoscript/src/main/scala/layer/package.scala | 67 ++++++++ geoscript/src/main/scala/render/package.scala | 2 +- .../src/main/scala/workspace/Workspace.scala | 6 +- geoscript/src/test/scala/UsageTests.scala | 10 +- 14 files changed, 112 insertions(+), 183 deletions(-) delete mode 100644 geoscript/src/main/scala/layer/Layer.scala create mode 100644 geoscript/src/main/scala/layer/package.scala diff --git a/examples/src/main/scala/example/AllValid.scala b/examples/src/main/scala/example/AllValid.scala index 01d0464..55fb241 100644 --- a/examples/src/main/scala/example/AllValid.scala +++ b/examples/src/main/scala/example/AllValid.scala @@ -5,7 +5,9 @@ import org.geoscript._ object AllValid extends App { val shp = layer.Shapefile(args.head) - val invalid = shp.features filter { f => !f.geometry.isValid } toSeq + val invalid = shp.withAll { fs => + fs.filter { f => !f.geometry.isValid }.toIndexedSeq + } println("Found %s invalid features.".format(invalid.size)) for (f <- invalid) println(f.id) diff --git a/examples/src/main/scala/example/ColorRamp.scala b/examples/src/main/scala/example/ColorRamp.scala index 93f5d67..9cf298c 100644 --- a/examples/src/main/scala/example/ColorRamp.scala +++ b/examples/src/main/scala/example/ColorRamp.scala @@ -23,9 +23,9 @@ object ColorRamp extends org.geoscript.feature.GeoCrunch { "#%02x02x02x".format(c.getRed, c.getGreen, c.getBlue) def colorRamp(data: layer.Layer, propertyName: String): style.Style = { - val propertyView = data.features.view.map(f => f.get[Double](propertyName)) - val min = propertyView.min - val max = propertyView.max + val extract = (f: feature.Feature) => f.get[Double](propertyName) + val min = data.withAll { fs => (fs map extract).min } + val max = data.withAll { fs => (fs map extract).max } val k = 10 val breaks = for (i <- (0 to k)) yield (i * max + (k - i) * min) / k diff --git a/examples/src/main/scala/example/FirstProject.scala b/examples/src/main/scala/example/FirstProject.scala index 5b37b28..5cd378b 100644 --- a/examples/src/main/scala/example/FirstProject.scala +++ b/examples/src/main/scala/example/FirstProject.scala @@ -4,7 +4,9 @@ import org.geoscript._ object FirstProject extends App { val shp = layer.Shapefile(args(0)) - val length = shp.features.map(_.geometry.length).sum + val length = shp.withAll { features => + features.map(_.geometry.length).sum + } println("Total Length %f".format(length)); } diff --git a/examples/src/main/scala/example/Intersections.scala b/examples/src/main/scala/example/Intersections.scala index 3b11d26..5657dc6 100644 --- a/examples/src/main/scala/example/Intersections.scala +++ b/examples/src/main/scala/example/Intersections.scala @@ -6,17 +6,21 @@ object Intersections { def process(src: layer.Layer, dest: layer.Layer, joinField: String) { println("Processing %s".format(src.schema.name)) - for (feat <- src.features) { - val intersections = - src.filter(filter.Filter.intersects(feat.geometry)) - dest ++= - intersections.filter(_.id > feat.id).map { corner => - feature.Feature( - "geom" -> (feat.geometry intersection corner.geometry), - (joinField + "Left") -> feat.get[Any](joinField), - (joinField + "Right") -> corner.get[Any](joinField) - ) + src.withAll { features => + for (feat <- features) { + src.withFiltered(filter.Filter.intersects(feat.geometry)) { + intersections => + + dest ++= + intersections.filter(_.id > feat.id).map { corner => + feature.Feature( + "geom" -> (feat.geometry intersection corner.geometry), + (joinField + "Left") -> feat.get[Any](joinField), + (joinField + "Right") -> corner.get[Any](joinField) + ) + } } + } } println("Found %d intersections".format(dest.count)) diff --git a/examples/src/main/scala/example/Postgis.scala b/examples/src/main/scala/example/Postgis.scala index c8e2628..d87f91b 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -18,6 +18,6 @@ object PostgisTest extends App { test += Feature( "name" -> "test", - "geom" -> geometry.Point(43,74) + "geom" -> geometry.point(43,74) ) } diff --git a/examples/src/main/scala/example/Render.scala b/examples/src/main/scala/example/Render.scala index 977b257..23a422a 100644 --- a/examples/src/main/scala/example/Render.scala +++ b/examples/src/main/scala/example/Render.scala @@ -1,8 +1,7 @@ package org.geoscript.example object Render extends App { - import org.geoscript.{ layer, style, render, projection }, - render.{ draw, png } + import org.geoscript._, render.{ draw, png } def reference(e: org.geoscript.geometry.Envelope, p: projection.Projection) = new org.geotools.geometry.jts.ReferencedEnvelope(e, p) diff --git a/examples/src/main/scala/example/Shp2Shp.scala b/examples/src/main/scala/example/Shp2Shp.scala index b3b8357..9e4a6aa 100644 --- a/examples/src/main/scala/example/Shp2Shp.scala +++ b/examples/src/main/scala/example/Shp2Shp.scala @@ -13,7 +13,7 @@ object Shp2Shp extends App { } ) val dest = source.workspace.create(destSchema) - dest ++= source.features map { f => - f.update(destSchema.geometry.name -> (f.geometry /* in proj */)) + source.withAll { fs => + dest ++= fs.toIterable } } diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 0ec4689..b4f39f5 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -1,6 +1,7 @@ package org package object geoscript { import geometry._ + import layer._ implicit def enrichGeometry(g: Geometry): RichGeometry = new RichGeometry(g) @@ -10,4 +11,7 @@ package object geoscript { implicit def enrichEnvelope(e: Envelope) = new RichEnvelope(e) + + implicit def enrichLayer(layer: Layer): RichLayer = + new RichLayer(layer) } diff --git a/geoscript/src/main/scala/feature/Feature.scala b/geoscript/src/main/scala/feature/Feature.scala index df3fa9b..f7ef4b0 100644 --- a/geoscript/src/main/scala/feature/Feature.scala +++ b/geoscript/src/main/scala/feature/Feature.scala @@ -226,6 +226,8 @@ trait Feature { for ((k, v) <- properties) feature.setAttribute(k, v) } + def underlying: org.opengis.feature.simple.SimpleFeature + override def toString: String = properties map { case (key, value: jts.Geometry) => @@ -262,6 +264,8 @@ object Feature { } yield (key -> value) pairs.toMap } + + def underlying = wrapped } } @@ -285,6 +289,8 @@ object Feature { def get[A](key: String): A = props.find(_._1 == key).map(_._2.asInstanceOf[A]).get + def underlying: org.opengis.feature.simple.SimpleFeature = sys.error("Unimplemented") + def properties: Map[String, Any] = Map(props.toSeq: _*) } } diff --git a/geoscript/src/main/scala/layer/Layer.scala b/geoscript/src/main/scala/layer/Layer.scala deleted file mode 100644 index a7d3194..0000000 --- a/geoscript/src/main/scala/layer/Layer.scala +++ /dev/null @@ -1,153 +0,0 @@ -package org.geoscript.layer - -import java.io.File - -import org.opengis.feature.simple.{ SimpleFeature, SimpleFeatureType } -import org.opengis.feature.`type`.{ AttributeDescriptor, GeometryDescriptor } -import org.{ geotools => gt } -import com.vividsolutions.jts.{ geom => jts } - -import org.geoscript.feature._ -import org.geoscript.filter._ -import org.geoscript.geometry._ -import org.geoscript.projection._ -import org.geoscript.workspace.{Directory,Workspace} - -/** - * A Layer represents a geospatial dataset. - */ -trait Layer { - /** - * The name of this data set - */ - val name: String - - /** - * The GeoTools datastore that is wrapped by this Layer. - */ - def store: gt.data.DataStore - - /** - * The workspace containing this layer. - */ - def workspace: Workspace - - /** - * Retrieve a GeoTools feature source for this layer. - */ - def source = store.getFeatureSource(name) - - /** - * The Schema describing this layer's contents. - */ - def schema: Schema = Schema(store.getSchema(name)) - - /** - * Get a feature collection that supports the typical Scala collection - * operations. - */ - def features: FeatureCollection = { - new FeatureCollection(source, new gt.data.Query()) - } - - /** - * Get a filtered feature collection. - */ - def filter(pred: Filter): FeatureCollection = { - new FeatureCollection(source, new gt.data.Query(name, pred.underlying)) - } - - /** - * Get the number of features currently in the layer. - */ - def count: Int = source.getCount(new gt.data.Query()) - - /** - * Get the bounding box of this Layer, in the format: - */ - def envelope: Envelope = source.getBounds() // in schema.geometry.projection - - /** - * Add a single Feature to this data set. - */ - def += (f: Feature) { this ++= Seq(f) } - - /** - * Add multiple features to this data set. This should be preferred over - * repeated use of += when adding multiple features. - */ - def ++= (features: Traversable[Feature]) { - val tx = new gt.data.DefaultTransaction - val writer = store.getFeatureWriterAppend(name, tx) - - try { - for (f <- features) { - val toBeWritten = writer.next() - f.writeTo(toBeWritten) - writer.write() - } - tx.commit() - } catch { - case ex => - tx.rollback() - throw ex - } finally { - writer.close() - tx.close() - } - } - - def -= (feature: Feature) { this --= Seq(feature) } - - def --= (features: Traversable[Feature]) { - exclude(Filter.or( - features.toSeq filter { null != } map { f => Filter.id(Seq(f.id)) } - )) - } - - def exclude(filter: Filter) { - store.getFeatureSource(name) - .asInstanceOf[gt.data.FeatureStore[SimpleFeatureType, SimpleFeature]] - .removeFeatures(filter.underlying) - } - - def update(replace: Feature => Feature) { - update(Filter.Include)(replace) - } - - def update(filter: Filter)(replace: Feature => Feature) { - val tx = new gt.data.DefaultTransaction - val writer = filter match { - case Filter.Include => store.getFeatureWriter(name, tx) - case filter => store.getFeatureWriter(name, filter.underlying, tx) - } - - while (writer hasNext) { - val existing = writer.next() - replace(Feature(existing)).writeTo(existing) - writer.write() - } - - writer.close() - tx.commit() - tx.close() - } - - override def toString: String = - "".format(name, count) -} - -/** - * Handy object for loading a Shapefile directly. The Shapefile layer is - * implicitly created in a Directory datastore. - */ -object Shapefile { - private def basename(f: File) = f.getName().replaceFirst("\\.[^.]+$", "") - - def apply(path: String): Layer = apply(new File(path)) - - def apply(file: File): Layer = { - val ws = Directory(file.getParent()) - ws.layer(basename(file)) - } -} diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala new file mode 100644 index 0000000..5042a6f --- /dev/null +++ b/geoscript/src/main/scala/layer/package.scala @@ -0,0 +1,67 @@ +package org.geoscript + +import scala.collection.JavaConverters._ + +import filter.Filter +import workspace.Workspace + +package object layer { + type Layer = org.geotools.data.simple.SimpleFeatureStore + type Query = org.geotools.data.Query +} + +package layer { + class RichLayer(layer: Layer) { + def count: Int = layer.getCount(new Query) + def envelope: geometry.Envelope = layer.getBounds + def name: String = layer.getName.getLocalPart + def schema: feature.Schema = feature.Schema(layer.getSchema) + def withAll[A](f: Iterator[feature.Feature] => A): A = + this.withCollection(layer.getFeatures())(f) + + private def withCollection[A] + (collection: org.geotools.data.simple.SimpleFeatureCollection) + (f: Iterator[feature.Feature] => A) + : A + = { + val iter = collection.features() + val features = new Iterator[feature.Feature] { + def hasNext: Boolean = iter.hasNext + def next: feature.Feature = feature.Feature(iter.next) + } + try + f(features) + finally + iter.close() + } + + def withFiltered[A] + (filter: Filter) + (f: Iterator[feature.Feature] => A) + : A + = this.withCollection(layer.getFeatures(filter))(f) + + def workspace: Workspace = layer.getDataStore().asInstanceOf[Workspace] + + def += (fs: feature.Feature*) = this ++= fs + + def ++= (features: Iterable[feature.Feature]) { + layer.getFeatures().addAll(features.map(_.underlying).asJavaCollection) + } + + def ++= (features: Iterator[feature.Feature]) { + this ++= features.toIterable + } + } + + object Shapefile { + private def basename(f: java.io.File) = + f.getName().replaceFirst("\\.[^.]+$", "") + + def apply(path: String): Layer = apply(new java.io.File(path)) + def apply(path: java.io.File): Layer = { + val ws = workspace.Directory(path.getParent()) + ws.layer(basename(path)) + } + } +} diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index d85ea4f..3d1e8d9 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -18,7 +18,7 @@ package render { new Stylable[layer.Layer] { override def applyStyle(lyr: layer.Layer, s: style.Style) : gt.map.Layer = - new gt.map.FeatureLayer(lyr.source, s.underlying, lyr.name) + new gt.map.FeatureLayer(lyr, s.underlying, lyr.name) override def boundsOf(lyr: layer.Layer): ReferencedEnvelope = new ReferencedEnvelope(lyr.envelope, lyr.schema.geometry.projection) diff --git a/geoscript/src/main/scala/workspace/Workspace.scala b/geoscript/src/main/scala/workspace/Workspace.scala index 6a2ee60..344a28e 100644 --- a/geoscript/src/main/scala/workspace/Workspace.scala +++ b/geoscript/src/main/scala/workspace/Workspace.scala @@ -12,11 +12,7 @@ class Workspace( ) { def count = underlying.getTypeNames.length def names: Seq[String] = underlying.getTypeNames - def layer(theName: String): Layer = new Layer { - val name = theName - val workspace = Workspace.this - val store = underlying - } + def layer(theName: String): Layer = underlying.getFeatureSource(theName).asInstanceOf[Layer] def create(name: String, fields: Field*): Layer = create(name, fields) diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index e5276e7..6c64ab4 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -56,7 +56,9 @@ class UsageTests extends Specification { "support search" in { val shp = layer.Shapefile(statesPath) - shp.features.find(_.id == "states.1") must beSome[feature.Feature] + shp.withAll { fs => + fs.find(_.id == "states.1") + } must beSome[feature.Feature] } "provide access to schema information" in { @@ -100,9 +102,9 @@ class UsageTests extends Specification { dummy.count must_== 2 - dummy.features.find( - f => f.get[String]("name") == "New York" - ) must beSome[feature.Feature] + dummy.withAll { fs => + fs.find(_.get[String]("name") == "New York") + } must beSome[feature.Feature] } } } From 116f087e24d26fa3f1eff0861babf8f7dce0d127 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 15 May 2012 21:33:59 -0400 Subject: [PATCH 16/53] Workspace stuff. --- examples/src/main/scala/example/Postgis.scala | 32 +-- geoscript/src/main/scala/Converters.scala | 7 + .../src/main/scala/feature/Feature.scala | 5 + geoscript/src/main/scala/layer/package.scala | 2 +- .../src/main/scala/workspace/Workspace.scala | 192 +++++++++--------- 5 files changed, 127 insertions(+), 111 deletions(-) diff --git a/examples/src/main/scala/example/Postgis.scala b/examples/src/main/scala/example/Postgis.scala index d87f91b..9ce1b8b 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -5,19 +5,23 @@ import org.geoscript._ import feature.{ Feature, Field } object PostgisTest extends App { - val conflict = workspace.Postgis("database" -> "conflict") - val fields = conflict.layer("conflictsite").schema.fields - - for (field <- fields) println(field.name) - val workSpaceTest = workspace.Postgis() - - val test = workSpaceTest.create("test", - Field("name", classOf[String]), - Field("geom", classOf[Geometry], "EPSG:4326") - ) + val params = workspace.Params.postgis("conflict") - test += Feature( - "name" -> "test", - "geom" -> geometry.point(43,74) - ) + workspace.withWorkspace(params) { conflict => + val fields = conflict.layerNamed("conflictsite").schema.fields + + for (field <- fields) println(field.name) + + workspace.withWorkspace(workspace.Params.postgis("test")) { wsTest => + val test = wsTest.create(feature.Schema("test", + Field("name", classOf[String]), + Field("geom", classOf[Geometry], "EPSG:4326") + )) + + test += Feature( + "name" -> "test", + "geom" -> geometry.point(43,74) + ) + } + } } diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index b4f39f5..24a776e 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -2,6 +2,7 @@ package org package object geoscript { import geometry._ import layer._ + import workspace._ implicit def enrichGeometry(g: Geometry): RichGeometry = new RichGeometry(g) @@ -14,4 +15,10 @@ package object geoscript { implicit def enrichLayer(layer: Layer): RichLayer = new RichLayer(layer) + + implicit def enrichWorkspace(workspace: Workspace): RichWorkspace = + new RichWorkspace(workspace) + + implicit def enrichConnector(connector: Connector): RichConnector = + new RichConnector(connector) } diff --git a/geoscript/src/main/scala/feature/Feature.scala b/geoscript/src/main/scala/feature/Feature.scala index f7ef4b0..22aa2c0 100644 --- a/geoscript/src/main/scala/feature/Feature.scala +++ b/geoscript/src/main/scala/feature/Feature.scala @@ -60,6 +60,8 @@ trait Schema { Feature(data: _*) } + def underlying: SimpleFeatureType + override def toString: String = { "".format( name, @@ -86,6 +88,8 @@ object Schema { } def get(fieldName: String) = Field(wrapped.getDescriptor(fieldName)) + + def underlying = wrapped } } @@ -101,6 +105,7 @@ object Schema { def fields = f.toSeq def get(fieldName: String) = f.find(_.name == fieldName).get + def underlying = sys.error("Unimplemented") } } } diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 5042a6f..4812634 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -61,7 +61,7 @@ package layer { def apply(path: String): Layer = apply(new java.io.File(path)) def apply(path: java.io.File): Layer = { val ws = workspace.Directory(path.getParent()) - ws.layer(basename(path)) + ws.layerNamed(basename(path)) } } } diff --git a/geoscript/src/main/scala/workspace/Workspace.scala b/geoscript/src/main/scala/workspace/Workspace.scala index 344a28e..b4ad94f 100644 --- a/geoscript/src/main/scala/workspace/Workspace.scala +++ b/geoscript/src/main/scala/workspace/Workspace.scala @@ -6,99 +6,99 @@ import org.geoscript.layer._ import org.{geotools => gt} import scala.collection.JavaConversions._ -class Workspace( - val underlying: gt.data.DataStore, - val params: java.util.HashMap[String, java.io.Serializable] -) { - def count = underlying.getTypeNames.length - def names: Seq[String] = underlying.getTypeNames - def layer(theName: String): Layer = underlying.getFeatureSource(theName).asInstanceOf[Layer] - - def create(name: String, fields: Field*): Layer = create(name, fields) - - def create(name: String, fields: Traversable[Field]): Layer = { - val builder = new gt.feature.simple.SimpleFeatureTypeBuilder - builder.setName(name) - fields foreach { - case field: GeoField => - builder.crs(field.projection.crs) - builder.add(field.name, field.gtBinding) - case field => - builder.add(field.name, field.gtBinding) - } - underlying.createSchema(builder.buildFeatureType()) - layer(name) - } - - def create(schema: Schema): Layer = create(schema.name, schema.fields: _*) - override def toString = "".format(params) -} - -object Workspace { - def apply(params: Pair[String, java.io.Serializable]*): Workspace = { - val jparams = new java.util.HashMap[String, java.io.Serializable]() - jparams.putAll(params.toMap[String, java.io.Serializable]) - new Workspace( - org.geotools.data.DataStoreFinder.getDataStore(jparams), - jparams - ) - } -} - -object Memory { - def apply() = - new Workspace( - new gt.data.memory.MemoryDataStore(), - new java.util.HashMap - ) -} - -object Postgis { - val factory = new gt.data.postgis.PostgisNGDataStoreFactory - val create: (java.util.HashMap[_,_]) => gt.data.DataStore = - factory.createDataStore - - def apply(params: (String,java.io.Serializable)*) = { - val connection = new java.util.HashMap[String,java.io.Serializable] - connection.put("port", "5432") - connection.put("host", "localhost") - connection.put("user", "postgres") - connection.put("passwd","") - connection.put("charset","utf-8") - connection.put("dbtype", "postgis") - for ((key,value) <- params) { - connection.put(key,value) - } - new Workspace(create(connection), connection) - } -} - -object SpatiaLite { - val factory = new gt.data.spatialite.SpatiaLiteDataStoreFactory - private val create: (java.util.HashMap[_,_]) => gt.data.DataStore = - factory.createDataStore - - def apply(params: (String,java.io.Serializable)*) = { - val connection = new java.util.HashMap[String,java.io.Serializable] - connection.put("dbtype","spatialite") - for ((key,value) <- params) { - connection.put(key,value) - } - new Workspace(create(connection), connection) - } -} - -object Directory { - private val factory = new gt.data.shapefile.ShapefileDataStoreFactory - - def apply(path: String): Workspace = apply(new File(path)) - - def apply(path: File): Workspace = { - val params = new java.util.HashMap[String, java.io.Serializable] - params.put("url", path.toURI.toURL) - val store = factory.createDataStore(params: java.util.Map[_, _]) - new Workspace(store, params) { - override def toString = "".format(params.get("url")) - } - } -} +// class Workspace( +// val underlying: gt.data.DataStore, +// val params: java.util.HashMap[String, java.io.Serializable] +// ) { +// def count = underlying.getTypeNames.length +// def names: Seq[String] = underlying.getTypeNames +// def layer(theName: String): Layer = underlying.getFeatureSource(theName).asInstanceOf[Layer] +// +// def create(name: String, fields: Field*): Layer = create(name, fields) +// +// def create(name: String, fields: Traversable[Field]): Layer = { +// val builder = new gt.feature.simple.SimpleFeatureTypeBuilder +// builder.setName(name) +// fields foreach { +// case field: GeoField => +// builder.crs(field.projection.crs) +// builder.add(field.name, field.gtBinding) +// case field => +// builder.add(field.name, field.gtBinding) +// } +// underlying.createSchema(builder.buildFeatureType()) +// layer(name) +// } +// +// def create(schema: Schema): Layer = create(schema.name, schema.fields: _*) +// override def toString = "".format(params) +// } +// +// object Workspace { +// def apply(params: Pair[String, java.io.Serializable]*): Workspace = { +// val jparams = new java.util.HashMap[String, java.io.Serializable]() +// jparams.putAll(params.toMap[String, java.io.Serializable]) +// new Workspace( +// org.geotools.data.DataStoreFinder.getDataStore(jparams), +// jparams +// ) +// } +// } +// +// object Memory { +// def apply() = +// new Workspace( +// new gt.data.memory.MemoryDataStore(), +// new java.util.HashMap +// ) +// } +// +// object Postgis { +// val factory = new gt.data.postgis.PostgisNGDataStoreFactory +// val create: (java.util.HashMap[_,_]) => gt.data.DataStore = +// factory.createDataStore +// +// def apply(params: (String,java.io.Serializable)*) = { +// val connection = new java.util.HashMap[String,java.io.Serializable] +// connection.put("port", "5432") +// connection.put("host", "localhost") +// connection.put("user", "postgres") +// connection.put("passwd","") +// connection.put("charset","utf-8") +// connection.put("dbtype", "postgis") +// for ((key,value) <- params) { +// connection.put(key,value) +// } +// new Workspace(create(connection), connection) +// } +// } +// +// object SpatiaLite { +// val factory = new gt.data.spatialite.SpatiaLiteDataStoreFactory +// private val create: (java.util.HashMap[_,_]) => gt.data.DataStore = +// factory.createDataStore +// +// def apply(params: (String,java.io.Serializable)*) = { +// val connection = new java.util.HashMap[String,java.io.Serializable] +// connection.put("dbtype","spatialite") +// for ((key,value) <- params) { +// connection.put(key,value) +// } +// new Workspace(create(connection), connection) +// } +// } +// +// object Directory { +// private val factory = new gt.data.shapefile.ShapefileDataStoreFactory +// +// def apply(path: String): Workspace = apply(new File(path)) +// +// def apply(path: File): Workspace = { +// val params = new java.util.HashMap[String, java.io.Serializable] +// params.put("url", path.toURI.toURL) +// val store = factory.createDataStore(params: java.util.Map[_, _]) +// new Workspace(store, params) { +// override def toString = "".format(params.get("url")) +// } +// } +// } From 138cb9bdee6f0c28a53544a74a44f2585e07ee6a Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 15 May 2012 21:34:07 -0400 Subject: [PATCH 17/53] Build tweaks to make "publish-local" useful --- project/Build.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index e30011d..92b52d4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -7,7 +7,7 @@ object GeoScript extends Build { val meta = Seq[Setting[_]]( organization := "org.geoscript", - version := "0.7.4", + version := "0.8.0", gtVersion := "8.0-M4", scalaVersion := "2.9.1", scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked") @@ -47,7 +47,7 @@ object GeoScript extends Build { lazy val examples = Project("examples", file("examples"), settings = common) dependsOn(library) lazy val library = - Project("library", file("geoscript"), settings = sphinxSettings ++ common) dependsOn(css, dummy) + Project("library", file("geoscript"), settings = sphinxSettings ++ common) dependsOn(css) lazy val support = Project("support", file("support"), settings = common) lazy val dummy = From 6dbacebb7f883794a24dd8d7165aee43534e4725 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 15 May 2012 21:51:03 -0400 Subject: [PATCH 18/53] Tweaks to get test code compiling again --- .../src/main/scala/workspace/package.scala | 154 ++++++++++++++++++ geoscript/src/test/scala/UsageTests.scala | 53 +++--- .../org/geoscript/workspaces/MemorySpec.scala | 15 +- 3 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 geoscript/src/main/scala/workspace/package.scala diff --git a/geoscript/src/main/scala/workspace/package.scala b/geoscript/src/main/scala/workspace/package.scala new file mode 100644 index 0000000..54dc6ee --- /dev/null +++ b/geoscript/src/main/scala/workspace/package.scala @@ -0,0 +1,154 @@ +package org.geoscript + +import layer._ + +import scala.collection.JavaConverters._ + +import org.geotools.data.DataStoreFinder.getDataStore + +package object workspace { + type Connector = org.geotools.data.DataStoreFactorySpi + type Workspace = org.geotools.data.DataStore + + def Workspace(params: (String, java.io.Serializable)*): Workspace = + Workspace(params.toMap) + + def Workspace(params: Map[String, java.io.Serializable]): Workspace = + getDataStore(params.asJava) + + def withWorkspace[A] + (params: (String, java.io.Serializable)*) + (f: Workspace => A) + : A + = withWorkspace(params.toMap)(f) + + def withWorkspace[A] + (params: Map[String, java.io.Serializable]) + (f: Workspace => A) + : A + = { + val workspace = getDataStore(params.asJava) + if (workspace == null) sys.error("No datastore loaded for params: " + params) + try + f(workspace) + finally + workspace.dispose() + } + + def withMemoryWorkspace[A](f: Workspace => A): A = { + val workspace = new org.geotools.data.memory.MemoryDataStore() + try + f(workspace) + finally + workspace.dispose() + } +} + +package workspace { + object Params { + import java.io.Serializable + private type Params = Map[String, Serializable] + + def directory(path: java.io.File): Params = { + import org.geotools.data.shapefile.ShapefileDirectoryFactory.URLP + Map(URLP.key -> path.toURI.toURL) + } + + def database( + dbtype: String, + database: String, + host: String = null, + port: Int = -1, + maxOpenPreparedStatements: Int = 0, + minConnections: Int = 0, + maxConnections: Int = 0, + maxWait: Int = 0, + exposePrimaryKeys: Boolean = false, + validateConnections: Boolean = true) + : Params + = { + import org.geotools.jdbc.JDBCDataStoreFactory._ + Map( + DBTYPE.key -> Option(dbtype), + DATABASE.key -> Option(database), + HOST.key -> Option(host), + PORT.key -> Some(port).filter(_ > 0), + MAX_OPEN_PREPARED_STATEMENTS.key -> Some(maxOpenPreparedStatements).filter(_ > 0), + MINCONN.key -> Some(minConnections).filter(_ > 0), + MAXCONN.key -> Some(maxConnections).filter(_ > 0), + MAXWAIT.key -> Some(maxWait).filter(_ > 0), + EXPOSE_PK.key -> Some(exposePrimaryKeys), + VALIDATECONN.key -> Some(validateConnections) + ).collect { + case (k, Some(v)) => (k, v.asInstanceOf[Serializable]) + } + } + + def postgis( + database: String, + host: String = null, + port: Int = -1, + maxOpenPreparedStatements: Int = 0, + minConnections: Int = 0, + maxConnections: Int = 0, + maxWait: Int = 0, + exposePrimaryKeys: Boolean = false, + validateConnections: Boolean = true) + : Params + = { + import org.geotools.jdbc.JDBCDataStoreFactory._ + Map( + DBTYPE.key -> Option("postgis"), + DATABASE.key -> Option(database), + HOST.key -> Option(host), + PORT.key -> Some(port).filter(_ > 0), + MAX_OPEN_PREPARED_STATEMENTS.key -> Some(maxOpenPreparedStatements).filter(_ > 0), + MINCONN.key -> Some(minConnections).filter(_ > 0), + MAXCONN.key -> Some(maxConnections).filter(_ > 0), + MAXWAIT.key -> Some(maxWait).filter(_ > 0), + EXPOSE_PK.key -> Some(exposePrimaryKeys), + VALIDATECONN.key -> Some(validateConnections) + ).collect { + case (k, Some(v)) => (k, v.asInstanceOf[Serializable]) + } + } + } + + object Directory { + def apply(s: String): Workspace = apply(new java.io.File(s)) + def apply(f: java.io.File): Workspace = Workspace(Params.directory(f)) + } + + class RichWorkspace(ws: Workspace) { + def count = ws.getTypeNames.length + def create(schema: feature.Schema): Layer = { + ws.createSchema(schema.underlying) + layerNamed(schema.name) + } + + def layerNamed(name: String): Layer = + ws.getFeatureSource(name).asInstanceOf[Layer] + + def names: Seq[String] = ws.getTypeNames + } + + class RichConnector(connector: Connector) { + def withWorkspace[A] + (params: (String, java.io.Serializable)*) + (f: Workspace => A) + : A + = withWorkspace(params.toMap)(f) + + def withWorkspace[A] + (params: Map[String, java.io.Serializable]) + (f: Workspace => A) + : A + = { + val workspace = connector.createDataStore(params.asJava) + try + f(workspace) + finally + workspace.dispose() + } + } +} diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 6c64ab4..a15f744 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -76,35 +76,38 @@ class UsageTests extends Specification { } "Workspaces" should { + import workspace._, feature.Schema + "provide a listing of layers" in { - val mem = workspace.Memory() - mem.names must beEmpty + val names = withMemoryWorkspace { _.names } + names must beEmpty } "allow creating new layers" in { - val mem = workspace.Memory() - mem.names must beEmpty - var dummy = mem.create("dummy", - feature.Field("name", classOf[String]), - feature.Field("geom", classOf[com.vividsolutions.jts.geom.Geometry], "EPSG:4326") - ) - mem.names.length must_== 1 - - dummy += feature.Feature( - "name" -> "San Francisco", - "geom" -> point(37.78, -122.42) - ) - - dummy += feature.Feature( - "name" -> "New York", - "geom" -> point(40.47, -73.58) - ) - - dummy.count must_== 2 - - dummy.withAll { fs => - fs.find(_.get[String]("name") == "New York") - } must beSome[feature.Feature] + withMemoryWorkspace { mem => + mem.names must beEmpty + var dummy = mem.create(Schema("dummy", + feature.Field("name", classOf[String]), + feature.Field("geom", classOf[com.vividsolutions.jts.geom.Geometry], "EPSG:4326") + )) + mem.names.length must_== 1 + + dummy += feature.Feature( + "name" -> "San Francisco", + "geom" -> point(37.78, -122.42) + ) + + dummy += feature.Feature( + "name" -> "New York", + "geom" -> point(40.47, -73.58) + ) + + dummy.count must_== 2 + + dummy.withAll { fs => + fs.find(_.get[String]("name") == "New York") + } must beSome[feature.Feature] + } } } } diff --git a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index 770b867..e825b5c 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -10,13 +10,14 @@ class MemorySpec extends Specification { feature.Field("the_geom", classOf[geometry.Point], "EPSG:4326"), feature.Field("name", classOf[String]) ) - val ws = workspace.Memory() - val lyr = ws.create(schema) - lyr += feature.Feature( - "the_geom" -> geometry.point(0, 0), - "name" -> "test" - ) - lyr.envelope must not(throwAn[Exception]) + workspace.withMemoryWorkspace { ws => + val lyr = ws.create(schema) + lyr += feature.Feature( + "the_geom" -> geometry.point(0, 0), + "name" -> "test" + ) + lyr.envelope must not(throwAn[Exception]) + } } } } From 2422e01652c91180ab393d89a504a53f1794578d Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 15 May 2012 22:33:09 -0400 Subject: [PATCH 19/53] Schema and attribute API --- .../main/scala/example/Intersections.scala | 20 +- examples/src/main/scala/example/Postgis.scala | 9 +- examples/src/main/scala/example/Shp2Shp.scala | 2 +- geoscript/src/main/scala/Converters.scala | 16 +- .../src/main/scala/feature/Feature.scala | 378 +++++++++--------- .../src/main/scala/feature/package.scala | 48 +++ geoscript/src/main/scala/layer/package.scala | 2 +- geoscript/src/main/scala/render/package.scala | 2 +- .../src/main/scala/workspace/package.scala | 2 +- geoscript/src/test/scala/UsageTests.scala | 9 +- .../org/geoscript/workspaces/MemorySpec.scala | 9 +- 11 files changed, 263 insertions(+), 234 deletions(-) create mode 100644 geoscript/src/main/scala/feature/package.scala diff --git a/examples/src/main/scala/example/Intersections.scala b/examples/src/main/scala/example/Intersections.scala index 5657dc6..bef267a 100644 --- a/examples/src/main/scala/example/Intersections.scala +++ b/examples/src/main/scala/example/Intersections.scala @@ -26,17 +26,15 @@ object Intersections { println("Found %d intersections".format(dest.count)) } - def rewrite(schema: feature.Schema, fieldName: String): feature.Schema = - feature.Schema( + import feature.{ Field, Schema, bind } + def rewrite(schema: Schema, fieldName: String): Schema = + Schema( schema.name + "_intersections", - feature.Field( - "geom", - classOf[com.vividsolutions.jts.geom.Geometry], - schema.geometry.projection - ), - feature.Field(fieldName + "Left", classOf[String]), - feature.Field(fieldName + "Right", classOf[String]) - ) + Seq( + bind[geometry.Geometry]("geom", + schema.geometry.getCoordinateReferenceSystem), + bind[String](fieldName + "Left"), + bind[String](fieldName + "Right"))) def main(args: Array[String]) = { if (args.length == 0) { @@ -44,7 +42,7 @@ object Intersections { } else { val src = layer.Shapefile(args(0)) val joinField = - src.schema.fields.find { _.gtBinding == classOf[String] } match { + src.schema.fields.find { _.binding == classOf[String] } match { case Some(f) => f.name case None => "id" } diff --git a/examples/src/main/scala/example/Postgis.scala b/examples/src/main/scala/example/Postgis.scala index 9ce1b8b..309aa8d 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -2,7 +2,7 @@ package org.geoscript.example import com.vividsolutions.jts.geom.Geometry import org.geoscript._ -import feature.{ Feature, Field } +import feature.{ Feature, Field, Schema, bind } object PostgisTest extends App { val params = workspace.Params.postgis("conflict") @@ -13,10 +13,9 @@ object PostgisTest extends App { for (field <- fields) println(field.name) workspace.withWorkspace(workspace.Params.postgis("test")) { wsTest => - val test = wsTest.create(feature.Schema("test", - Field("name", classOf[String]), - Field("geom", classOf[Geometry], "EPSG:4326") - )) + val test = wsTest.create(Schema( + "test", + Seq(bind[String]("name"), bind[Geometry]("geom", "EPSG:4326")))) test += Feature( "name" -> "test", diff --git a/examples/src/main/scala/example/Shp2Shp.scala b/examples/src/main/scala/example/Shp2Shp.scala index 9e4a6aa..f9ec98a 100644 --- a/examples/src/main/scala/example/Shp2Shp.scala +++ b/examples/src/main/scala/example/Shp2Shp.scala @@ -8,7 +8,7 @@ object Shp2Shp extends App { val source = layer.Shapefile(sourcefile) val destSchema = Schema(destname, source.schema.fields map { - case (g: GeoField) => g.copy(projection = proj) + case (g: GeoField) => g.withProjection(proj) case (f: Field) => f } ) diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 24a776e..961e5a6 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -1,5 +1,6 @@ package org package object geoscript { + import feature._ import geometry._ import layer._ import workspace._ @@ -7,14 +8,17 @@ package object geoscript { implicit def enrichGeometry(g: Geometry): RichGeometry = new RichGeometry(g) - implicit def enrichPoint(p: Point): RichPoint = - new RichPoint(p) + implicit def enrichPoint(p: Point): RichPoint = new RichPoint(p) - implicit def enrichEnvelope(e: Envelope) = - new RichEnvelope(e) + implicit def enrichEnvelope(e: Envelope) = new RichEnvelope(e) - implicit def enrichLayer(layer: Layer): RichLayer = - new RichLayer(layer) + implicit def enrichField(f: Field) = new RichField(f) + + implicit def enrichGeoField(f: GeoField) = new RichGeoField(f) + + implicit def enrichSchema(s: Schema) = new RichSchema(s) + + implicit def enrichLayer(layer: Layer): RichLayer = new RichLayer(layer) implicit def enrichWorkspace(workspace: Workspace): RichWorkspace = new RichWorkspace(workspace) diff --git a/geoscript/src/main/scala/feature/Feature.scala b/geoscript/src/main/scala/feature/Feature.scala index 22aa2c0..2965736 100644 --- a/geoscript/src/main/scala/feature/Feature.scala +++ b/geoscript/src/main/scala/feature/Feature.scala @@ -7,185 +7,185 @@ import org.{geotools => gt} import org.opengis.feature.simple.{SimpleFeature, SimpleFeatureType} import org.opengis.feature.`type`.{AttributeDescriptor, GeometryDescriptor} -/** - * A Schema enumerates the types and names of the properties of records in a - * particular dataset. For example, a Schema for a road dataset might have - * fields like "name", "num_lanes", "the_geom". - */ -trait Schema { - /** - * The name of the dataset itself. This is not a property of the data records. - */ - def name: String - - /** - * The geometry field for this layer, regardless of its name. - */ - def geometry: GeoField - - /** - * All fields in an iterable sequence. - */ - def fields: Seq[Field] - - /** - * The names of all fields in an iterable sequence. - */ - def fieldNames: Seq[String] = fields map { _.name } - - /** - * Retrieve a field by name. - */ - def get(fieldName: String): Field - - /** - * Create an instance of a feature according to this Schema, erroring if: - *
    - *
  • any values are omitted
  • - *
  • any values are the wrong type
  • - *
  • any extra values are present
  • - *
- */ - def create(data: (String, AnyRef)*): Feature = { - if ( - data.length != fields.length || - data.exists { case (key, value) => !get(key).gtBinding.isInstance(value) } - ) { - throw new RuntimeException( - "Can't create feature; properties are: %s, but fields require %s.".format( - data.mkString, fields.mkString - ) - ) - } - Feature(data: _*) - } - - def underlying: SimpleFeatureType - - override def toString: String = { - "".format( - name, - fields.mkString("[", ", ", "]") - ) - } -} - -/** - * A companion object for Schema that provides various ways of creating Schema - * instances. - */ -object Schema { - def apply(wrapped: SimpleFeatureType) = { - new Schema { - def name = wrapped.getTypeName() - def geometry = Field(wrapped.getGeometryDescriptor()) - - def fields: Seq[Field] = { - var buffer = new collection.mutable.ArrayBuffer[Field] - val descriptors = wrapped.getAttributeDescriptors().iterator() - while (descriptors hasNext) { buffer += Field(descriptors.next) } - buffer.toSeq - } - - def get(fieldName: String) = Field(wrapped.getDescriptor(fieldName)) - - def underlying = wrapped - } - } - - def apply(n: String, f: Field*): Schema = apply(n, f.toSeq) - - def apply(n: String, f: Iterable[Field]): Schema = { - new Schema { - def name = n - def geometry = - f.find(_.isInstanceOf[GeoField]) - .getOrElse(null) - .asInstanceOf[GeoField] - - def fields = f.toSeq - def get(fieldName: String) = f.find(_.name == fieldName).get - def underlying = sys.error("Unimplemented") - } - } -} - -/** - * A Field represents a particular named, typed property in a Schema. - */ -trait Field { - def name: String - def gtBinding: Class[_] - override def toString = "%s: %s".format(name, gtBinding.getSimpleName) -} - -/** - * A Field that represents a Geometry. GeoFields add projection information to - * normal fields. - */ -trait GeoField extends Field { - override def gtBinding: Class[_] - /** - * The Projection used for this field's geometry. - */ - def projection: Projection - - def copy(projection: Projection): GeoField = { - val n = name - val gb = gtBinding - val p = projection - - new GeoField { - val name = n - override val gtBinding = gb - val projection = p - } - } - - override def toString = "%s: %s [%s]".format(name, gtBinding.getSimpleName, projection) -} - -/** - * A companion object providing various methods of creating Field instances. - */ -object Field { - /** - * Create a GeoField by wrapping an OpenGIS GeometryDescriptor - */ - def apply(wrapped: GeometryDescriptor): GeoField = - new GeoField { - def name = wrapped.getLocalName - override def gtBinding = wrapped.getType.getBinding - def projection = Projection(wrapped.getCoordinateReferenceSystem()) - } - - /** - * Create a Field by wrapping an OpenGIS AttributeDescriptor - */ - def apply(wrapped: AttributeDescriptor): Field = { - wrapped match { - case geom: GeometryDescriptor => apply(geom) - case wrapped => - new Field { - def name = wrapped.getLocalName - def gtBinding = wrapped.getType.getBinding - } - } - } - - def apply[G : BoundGeometry](n: String, b: Class[G], p: Projection): GeoField = - new GeoField { - def name = n - def gtBinding = implicitly[BoundGeometry[G]].binding - def projection = p - } - - def apply[S : BoundScalar](n: String, b: Class[S]): Field = - new Field { - def name = n - def gtBinding = implicitly[BoundScalar[S]].binding - } -} +// /** +// * A Schema enumerates the types and names of the properties of records in a +// * particular dataset. For example, a Schema for a road dataset might have +// * fields like "name", "num_lanes", "the_geom". +// */ +// trait Schema { +// /** +// * The name of the dataset itself. This is not a property of the data records. +// */ +// def name: String +// +// /** +// * The geometry field for this layer, regardless of its name. +// */ +// def geometry: GeoField +// +// /** +// * All fields in an iterable sequence. +// */ +// def fields: Seq[Field] +// +// /** +// * The names of all fields in an iterable sequence. +// */ +// def fieldNames: Seq[String] = fields map { _.name } +// +// /** +// * Retrieve a field by name. +// */ +// def get(fieldName: String): Field +// +// /** +// * Create an instance of a feature according to this Schema, erroring if: +// *
    +// *
  • any values are omitted
  • +// *
  • any values are the wrong type
  • +// *
  • any extra values are present
  • +// *
+// */ +// def create(data: (String, AnyRef)*): Feature = { +// if ( +// data.length != fields.length || +// data.exists { case (key, value) => !get(key).gtBinding.isInstance(value) } +// ) { +// throw new RuntimeException( +// "Can't create feature; properties are: %s, but fields require %s.".format( +// data.mkString, fields.mkString +// ) +// ) +// } +// Feature(data: _*) +// } +// +// def underlying: SimpleFeatureType +// +// override def toString: String = { +// "".format( +// name, +// fields.mkString("[", ", ", "]") +// ) +// } +// } + +// /** +// * A companion object for Schema that provides various ways of creating Schema +// * instances. +// */ +// object Schema { +// def apply(wrapped: SimpleFeatureType) = { +// new Schema { +// def name = wrapped.getTypeName() +// def geometry = Field(wrapped.getGeometryDescriptor()) +// +// def fields: Seq[Field] = { +// var buffer = new collection.mutable.ArrayBuffer[Field] +// val descriptors = wrapped.getAttributeDescriptors().iterator() +// while (descriptors hasNext) { buffer += Field(descriptors.next) } +// buffer.toSeq +// } +// +// def get(fieldName: String) = Field(wrapped.getDescriptor(fieldName)) +// +// def underlying = wrapped +// } +// } +// +// def apply(n: String, f: Field*): Schema = apply(n, f.toSeq) +// +// def apply(n: String, f: Iterable[Field]): Schema = { +// new Schema { +// def name = n +// def geometry = +// f.find(_.isInstanceOf[GeoField]) +// .getOrElse(null) +// .asInstanceOf[GeoField] +// +// def fields = f.toSeq +// def get(fieldName: String) = f.find(_.name == fieldName).get +// def underlying = sys.error("Unimplemented") +// } +// } +// } + +// /** +// * A Field represents a particular named, typed property in a Schema. +// */ +// trait Field { +// def name: String +// def gtBinding: Class[_] +// override def toString = "%s: %s".format(name, gtBinding.getSimpleName) +// } +// +// /** +// * A Field that represents a Geometry. GeoFields add projection information to +// * normal fields. +// */ +// trait GeoField extends Field { +// override def gtBinding: Class[_] +// /** +// * The Projection used for this field's geometry. +// */ +// def projection: Projection +// +// def copy(projection: Projection): GeoField = { +// val n = name +// val gb = gtBinding +// val p = projection +// +// new GeoField { +// val name = n +// override val gtBinding = gb +// val projection = p +// } +// } +// +// override def toString = "%s: %s [%s]".format(name, gtBinding.getSimpleName, projection) +// } +// +// /** +// * A companion object providing various methods of creating Field instances. +// */ +// object Field { +// /** +// * Create a GeoField by wrapping an OpenGIS GeometryDescriptor +// */ +// def apply(wrapped: GeometryDescriptor): GeoField = +// new GeoField { +// def name = wrapped.getLocalName +// override def gtBinding = wrapped.getType.getBinding +// def projection = Projection(wrapped.getCoordinateReferenceSystem()) +// } +// +// /** +// * Create a Field by wrapping an OpenGIS AttributeDescriptor +// */ +// def apply(wrapped: AttributeDescriptor): Field = { +// wrapped match { +// case geom: GeometryDescriptor => apply(geom) +// case wrapped => +// new Field { +// def name = wrapped.getLocalName +// def gtBinding = wrapped.getType.getBinding +// } +// } +// } +// +// def apply[G : BoundGeometry](n: String, b: Class[G], p: Projection): GeoField = +// new GeoField { +// def name = n +// def gtBinding = implicitly[BoundGeometry[G]].binding +// def projection = p +// } +// +// def apply[S : BoundScalar](n: String, b: Class[S]): Field = +// new Field { +// def name = n +// def gtBinding = implicitly[BoundScalar[S]].binding +// } +// } /** * A Feature represents a record in a geospatial data set. It should generally @@ -300,23 +300,3 @@ object Feature { } } } - -/** - * A collection of features, possibly not all loaded yet. For example, queries - * against Layers produce feature collections, but the query may not actually - * be sent until you access the contents of the collection. - * - * End users will generally not need to create FeatureCollections directly. - */ -class FeatureCollection( - wrapped: gt.data.FeatureSource[SimpleFeatureType, SimpleFeature], - query: gt.data.Query -) extends Traversable[Feature] { - override def foreach[U](op: Feature => U) { - val iter = wrapped.getFeatures().features() - try - while (iter hasNext) op(Feature(iter.next)) - finally - iter.close() - } -} diff --git a/geoscript/src/main/scala/feature/package.scala b/geoscript/src/main/scala/feature/package.scala new file mode 100644 index 0000000..1b44a02 --- /dev/null +++ b/geoscript/src/main/scala/feature/package.scala @@ -0,0 +1,48 @@ +package org.geoscript + +import org.opengis.feature.`type`.AttributeDescriptor +import scala.collection.JavaConverters._ + +package object feature { + type Schema = org.opengis.feature.simple.SimpleFeatureType + type Field = org.opengis.feature.`type`.AttributeDescriptor + type GeoField = org.opengis.feature.`type`.GeometryDescriptor + + def bind[T : Manifest](name: String): Track[Field, T] = + sys.error("Unimplemented") + + def bind[T <: geometry.Geometry : Manifest] + (name: String, proj: projection.Projection): Track[GeoField, G] = sys.error("Unimplemented") +} + +package feature { + object Schema { + def apply(name: String, fields: Seq[Field]): Schema = { + val builder = new org.geotools.feature.simple.SimpleFeatureTypeBuilder + builder.setName(name) + for (field <- fields) { + builder.add(field) + } + builder.buildFeatureType() + } + } + + class RichSchema(schema: Schema) { + def name: String = schema.getName.getLocalPart + def fields: Seq[Field] = + schema.getAttributeDescriptors.asScala + def geometry = schema.getGeometryDescriptor + def get(name: String): Field = schema.getDescriptor(name) + def get(index: Int): Field = schema.getDescriptor(index) + def withName(name: String): Schema = sys.error("Unimplemented") + } + + class RichField(field: Field) { + def name: String = field.getName.getLocalPart + def binding: Class[_] = field.getType.getBinding + } + + class RichGeoField(field: GeoField) { + def withProjection(proj: projection.Projection): GeoField = sys.error("Unimplemented") + } +} diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 4812634..3cc8d24 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -15,7 +15,7 @@ package layer { def count: Int = layer.getCount(new Query) def envelope: geometry.Envelope = layer.getBounds def name: String = layer.getName.getLocalPart - def schema: feature.Schema = feature.Schema(layer.getSchema) + def schema: feature.Schema = layer.getSchema def withAll[A](f: Iterator[feature.Feature] => A): A = this.withCollection(layer.getFeatures())(f) diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index 3d1e8d9..f4748b5 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -21,7 +21,7 @@ package render { new gt.map.FeatureLayer(lyr, s.underlying, lyr.name) override def boundsOf(lyr: layer.Layer): ReferencedEnvelope = - new ReferencedEnvelope(lyr.envelope, lyr.schema.geometry.projection) + new ReferencedEnvelope(lyr.envelope, lyr.schema.geometry.getCoordinateReferenceSystem) } } diff --git a/geoscript/src/main/scala/workspace/package.scala b/geoscript/src/main/scala/workspace/package.scala index 54dc6ee..c4afe9f 100644 --- a/geoscript/src/main/scala/workspace/package.scala +++ b/geoscript/src/main/scala/workspace/package.scala @@ -122,7 +122,7 @@ package workspace { class RichWorkspace(ws: Workspace) { def count = ws.getTypeNames.length def create(schema: feature.Schema): Layer = { - ws.createSchema(schema.underlying) + ws.createSchema(schema) layerNamed(schema.name) } diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index a15f744..44a5168 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -66,7 +66,7 @@ class UsageTests extends Specification { shp.schema.name must_== "states" val field = shp.schema.get("STATE_NAME") field.name must_== "STATE_NAME" - (field.gtBinding: AnyRef) must_== classOf[java.lang.String] + (field.binding: AnyRef) must_== classOf[java.lang.String] } "provide access to the containing workspace" in { @@ -76,7 +76,7 @@ class UsageTests extends Specification { } "Workspaces" should { - import workspace._, feature.Schema + import workspace._, feature.{ Schema, bind } "provide a listing of layers" in { val names = withMemoryWorkspace { _.names } @@ -87,9 +87,8 @@ class UsageTests extends Specification { withMemoryWorkspace { mem => mem.names must beEmpty var dummy = mem.create(Schema("dummy", - feature.Field("name", classOf[String]), - feature.Field("geom", classOf[com.vividsolutions.jts.geom.Geometry], "EPSG:4326") - )) + Seq(bind[String]("name"), bind[Geometry]("geom", "EPSG:4326")))) + mem.names.length must_== 1 dummy += feature.Feature( diff --git a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index e825b5c..6ccbfb1 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -4,12 +4,13 @@ package workspace import org.specs._ class MemorySpec extends Specification { + import feature.{ Schema, bind } "Memory datastores" should { "be able to create layers" in { - val schema = feature.Schema("cities", - feature.Field("the_geom", classOf[geometry.Point], "EPSG:4326"), - feature.Field("name", classOf[String]) - ) + val schema = Schema("cities", + Seq( + bind[geometry.Point]("the_geom", "EPSG:4326"), + bind[String]("name"))) workspace.withMemoryWorkspace { ws => val lyr = ws.create(schema) lyr += feature.Feature( From c48ba0eca424e4fa6337bba6ca9f9f85711275ed Mon Sep 17 00:00:00 2001 From: David Winslow Date: Wed, 23 May 2012 21:02:35 -0400 Subject: [PATCH 20/53] move feature over to the new style --- .../main/scala/example/Intersections.scala | 2 +- examples/src/main/scala/example/Postgis.scala | 2 +- geoscript/src/main/scala/Converters.scala | 2 + .../src/main/scala/feature/Feature.scala | 216 +++++++++--------- .../src/main/scala/feature/package.scala | 16 +- geoscript/src/main/scala/layer/package.scala | 4 +- 6 files changed, 127 insertions(+), 115 deletions(-) diff --git a/examples/src/main/scala/example/Intersections.scala b/examples/src/main/scala/example/Intersections.scala index bef267a..7e3b6ca 100644 --- a/examples/src/main/scala/example/Intersections.scala +++ b/examples/src/main/scala/example/Intersections.scala @@ -13,7 +13,7 @@ object Intersections { dest ++= intersections.filter(_.id > feat.id).map { corner => - feature.Feature( + feature.fromAttributes( "geom" -> (feat.geometry intersection corner.geometry), (joinField + "Left") -> feat.get[Any](joinField), (joinField + "Right") -> corner.get[Any](joinField) diff --git a/examples/src/main/scala/example/Postgis.scala b/examples/src/main/scala/example/Postgis.scala index 309aa8d..a3e963d 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -17,7 +17,7 @@ object PostgisTest extends App { "test", Seq(bind[String]("name"), bind[Geometry]("geom", "EPSG:4326")))) - test += Feature( + test += feature.fromAttributes( "name" -> "test", "geom" -> geometry.point(43,74) ) diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 961e5a6..3bb0c53 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -14,6 +14,8 @@ package object geoscript { implicit def enrichField(f: Field) = new RichField(f) + implicit def enrichFeature(f: Feature) = new RichFeature(f) + implicit def enrichGeoField(f: GeoField) = new RichGeoField(f) implicit def enrichSchema(s: Schema) = new RichSchema(s) diff --git a/geoscript/src/main/scala/feature/Feature.scala b/geoscript/src/main/scala/feature/Feature.scala index 2965736..512340e 100644 --- a/geoscript/src/main/scala/feature/Feature.scala +++ b/geoscript/src/main/scala/feature/Feature.scala @@ -191,112 +191,112 @@ import org.opengis.feature.`type`.{AttributeDescriptor, GeometryDescriptor} * A Feature represents a record in a geospatial data set. It should generally * identify a single "thing" such as a landmark or observation. */ -trait Feature { - /** - * An identifier for this feature in the dataset. - */ - def id: String - - /** - * Retrieve a property of the feature, with an expected type. Typical usage is: - *
-   * val name = feature.get[String]("name")
-   * 
- */ - def get[A](key: String): A - - /** - * Get the geometry for this feature. This allows you to access the geometry - * without worrying about its property name. - */ - def geometry: Geometry - - /** - * Get all properties for this feature as a Map. - */ - def properties: Map[String, Any] - - def update(data: (String, Any)*): Feature = update(data.toSeq) - - def update(data: Iterable[(String, Any)]): Feature = { - val props = properties - assert(data.forall { x => props contains x._1 }) - Feature(props ++ data) - } - - /** - * Write the values in this Feature to a particular OGC Feature object. - */ - def writeTo(feature: org.opengis.feature.simple.SimpleFeature) { - for ((k, v) <- properties) feature.setAttribute(k, v) - } - - def underlying: org.opengis.feature.simple.SimpleFeature - - override def toString: String = - properties map { - case (key, value: jts.Geometry) => - "%s: <%s>".format(key, value.getGeometryType()) - case (key, value) => - "%s: %s".format(key, value) - } mkString("") -} - -/** - * A companion object for Feature providing several methods for creating - * Feature instances. - */ -object Feature { - /** - * Create a GeoScript feature by wrapping a GeoAPI feature instance. - */ - def apply(wrapped: SimpleFeature): Feature = { - new Feature { - def id: String = wrapped.getID - - def get[A](key: String): A = - wrapped.getAttribute(key).asInstanceOf[A] - - def geometry: Geometry = - wrapped.getDefaultGeometry().asInstanceOf[Geometry] - - def properties: Map[String, Any] = { - val pairs = - for { - i <- 0 until wrapped.getAttributeCount - key = wrapped.getType().getDescriptor(i).getLocalName - value = get[Any](key) - } yield (key -> value) - pairs.toMap - } - - def underlying = wrapped - } - } - - def apply(props: (String, Any)*): Feature = apply(props) - - /** - * Create a feature from name/value pairs. Example usage looks like: - *
-   * val feature = Feature("geom" -> Point(12, 37), "type" -> "radio tower")
-   * 
- */ - def apply(props: Iterable[(String, Any)]): Feature = { - new Feature { - def id: String = null - - def geometry = - props.collectFirst({ - case (name, geom: Geometry) => geom - }).get - - def get[A](key: String): A = - props.find(_._1 == key).map(_._2.asInstanceOf[A]).get - - def underlying: org.opengis.feature.simple.SimpleFeature = sys.error("Unimplemented") +// trait Feature { +// /** +// * An identifier for this feature in the dataset. +// */ +// def id: String +// +// /** +// * Retrieve a property of the feature, with an expected type. Typical usage is: +// *
+//    * val name = feature.get[String]("name")
+//    * 
+// */ +// def get[A](key: String): A +// +// /** +// * Get the geometry for this feature. This allows you to access the geometry +// * without worrying about its property name. +// */ +// def geometry: Geometry +// +// /** +// * Get all properties for this feature as a Map. +// */ +// def properties: Map[String, Any] +// +// def update(data: (String, Any)*): Feature = update(data.toSeq) +// +// def update(data: Iterable[(String, Any)]): Feature = { +// val props = properties +// assert(data.forall { x => props contains x._1 }) +// Feature(props ++ data) +// } +// +// /** +// * Write the values in this Feature to a particular OGC Feature object. +// */ +// def writeTo(feature: org.opengis.feature.simple.SimpleFeature) { +// for ((k, v) <- properties) feature.setAttribute(k, v) +// } +// +// def underlying: org.opengis.feature.simple.SimpleFeature +// +// override def toString: String = +// properties map { +// case (key, value: jts.Geometry) => +// "%s: <%s>".format(key, value.getGeometryType()) +// case (key, value) => +// "%s: %s".format(key, value) +// } mkString("") +// } - def properties: Map[String, Any] = Map(props.toSeq: _*) - } - } -} +// /** +// * A companion object for Feature providing several methods for creating +// * Feature instances. +// */ +// object Feature { +// /** +// * Create a GeoScript feature by wrapping a GeoAPI feature instance. +// */ +// def apply(wrapped: SimpleFeature): Feature = { +// new Feature { +// def id: String = wrapped.getID +// +// def get[A](key: String): A = +// wrapped.getAttribute(key).asInstanceOf[A] +// +// def geometry: Geometry = +// wrapped.getDefaultGeometry().asInstanceOf[Geometry] +// +// def properties: Map[String, Any] = { +// val pairs = +// for { +// i <- 0 until wrapped.getAttributeCount +// key = wrapped.getType().getDescriptor(i).getLocalName +// value = get[Any](key) +// } yield (key -> value) +// pairs.toMap +// } +// +// def underlying = wrapped +// } +// } +// +// def apply(props: (String, Any)*): Feature = apply(props) +// +// /** +// * Create a feature from name/value pairs. Example usage looks like: +// *
+//    * val feature = Feature("geom" -> Point(12, 37), "type" -> "radio tower")
+//    * 
+// */ +// def apply(props: Iterable[(String, Any)]): Feature = { +// new Feature { +// def id: String = null +// +// def geometry = +// props.collectFirst({ +// case (name, geom: Geometry) => geom +// }).get +// +// def get[A](key: String): A = +// props.find(_._1 == key).map(_._2.asInstanceOf[A]).get +// +// def underlying: org.opengis.feature.simple.SimpleFeature = sys.error("Unimplemented") +// +// def properties: Map[String, Any] = Map(props.toSeq: _*) +// } +// } +// } diff --git a/geoscript/src/main/scala/feature/package.scala b/geoscript/src/main/scala/feature/package.scala index 1b44a02..156fc56 100644 --- a/geoscript/src/main/scala/feature/package.scala +++ b/geoscript/src/main/scala/feature/package.scala @@ -4,15 +4,18 @@ import org.opengis.feature.`type`.AttributeDescriptor import scala.collection.JavaConverters._ package object feature { - type Schema = org.opengis.feature.simple.SimpleFeatureType + type Feature = org.opengis.feature.simple.SimpleFeature type Field = org.opengis.feature.`type`.AttributeDescriptor type GeoField = org.opengis.feature.`type`.GeometryDescriptor + type Schema = org.opengis.feature.simple.SimpleFeatureType - def bind[T : Manifest](name: String): Track[Field, T] = + def bind[T : Manifest](name: String): Field = sys.error("Unimplemented") def bind[T <: geometry.Geometry : Manifest] - (name: String, proj: projection.Projection): Track[GeoField, G] = sys.error("Unimplemented") + (name: String, proj: projection.Projection): GeoField = sys.error("Unimplemented") + + def fromAttributes(attributes: (String, Any)*): Feature = sys.error("Unimplemented") } package feature { @@ -37,6 +40,13 @@ package feature { def withName(name: String): Schema = sys.error("Unimplemented") } + class RichFeature(feature: Feature) { + def id: String = sys.error("unimplemented") + def get[A](index: Int): A = sys.error("Unimplemented") + def get[A](key: String): A = sys.error("Unimplemented") + def geometry: org.geoscript.geometry.Geometry = sys.error("Unimplemented") + } + class RichField(field: Field) { def name: String = field.getName.getLocalPart def binding: Class[_] = field.getType.getBinding diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 3cc8d24..33e9e47 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -27,7 +27,7 @@ package layer { val iter = collection.features() val features = new Iterator[feature.Feature] { def hasNext: Boolean = iter.hasNext - def next: feature.Feature = feature.Feature(iter.next) + def next: feature.Feature = iter.next } try f(features) @@ -46,7 +46,7 @@ package layer { def += (fs: feature.Feature*) = this ++= fs def ++= (features: Iterable[feature.Feature]) { - layer.getFeatures().addAll(features.map(_.underlying).asJavaCollection) + layer.getFeatures().addAll(features.asJavaCollection) } def ++= (features: Iterator[feature.Feature]) { From 0d92de0fbacac8c3cf2040502a972155ab8fb499 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Wed, 23 May 2012 21:08:05 -0400 Subject: [PATCH 21/53] Get rid of compiler warnings in render module --- geoscript/src/main/scala/render/package.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index f4748b5..cb639e3 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -90,14 +90,13 @@ package object render { = { val effectiveBounds = bounds.getOrElse(ev.boundsOf(t)) - val context = new gt.map.DefaultMapContext - context.setAreaOfInterest(effectiveBounds) - context.addLayer(ev.applyStyle(t, sty)) + val content = new gt.map.MapContent + content.addLayer(ev.applyStyle(t, sty)) val hints = renderHints(KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON) val renderer = new gt.renderer.lite.StreamingRenderer() renderer.setJava2DHints(hints) - renderer.setContext(context) + renderer.setMapContent(content) val (w, h) = size canvas.render(size, { g => renderer.paint(g, new java.awt.Rectangle(w, h), effectiveBounds) From dcc3ffe9d2103abf2273d9475b43c8e93bb22ab9 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 3 Jun 2012 14:23:03 -0400 Subject: [PATCH 22/53] More refactoring --- examples/src/main/scala/example/Render.scala | 6 +- geoscript/build.sbt | 15 +-- geoscript/src/main/scala/Converters.scala | 6 +- .../src/main/scala/geometry/io/package.scala | 13 +- geoscript/src/main/scala/io/Sink.scala | 46 ------- geoscript/src/main/scala/io/Source.scala | 51 -------- geoscript/src/main/scala/render/package.scala | 116 ++++++++++-------- geoscript/src/test/scala/UsageTests.scala | 4 +- .../geometry/SerializationSpec.scala | 18 +-- .../org/geoscript/workspaces/MemorySpec.scala | 2 +- 10 files changed, 92 insertions(+), 185 deletions(-) delete mode 100644 geoscript/src/main/scala/io/Sink.scala delete mode 100644 geoscript/src/main/scala/io/Source.scala diff --git a/examples/src/main/scala/example/Render.scala b/examples/src/main/scala/example/Render.scala index 23a422a..ecbcfa4 100644 --- a/examples/src/main/scala/example/Render.scala +++ b/examples/src/main/scala/example/Render.scala @@ -1,7 +1,7 @@ package org.geoscript.example object Render extends App { - import org.geoscript._, render.{ draw, png } + import org.geoscript._, render.{ Content, draw, png } def reference(e: org.geoscript.geometry.Envelope, p: projection.Projection) = new org.geotools.geometry.jts.ReferencedEnvelope(e, p) @@ -15,7 +15,7 @@ object Render extends App { val theme = style.CSS.fromFile(styleFile) val bounds = reference(states.envelope, projection.Projection("EPSG:4326")) val win = new org.geoscript.render.Window - draw(states, theme, Some(bounds), (512, 512), win) + draw(Content(states, theme), bounds, win) val watcher = new actors.DaemonActor { val styleFile = new java.io.File("../geocss/src/test/resources/states.css") @@ -26,7 +26,7 @@ object Render extends App { if (updated < lastModified) { try { val theme = style.CSS.fromFile("../geocss/src/test/resources/states.css") - draw(states, theme, Some(bounds), (512, 512), win) + draw(Content(states, theme), bounds, win) } catch { case _ => () } diff --git a/geoscript/build.sbt b/geoscript/build.sbt index 39f5c41..43d100e 100644 --- a/geoscript/build.sbt +++ b/geoscript/build.sbt @@ -1,9 +1,5 @@ name := "geoscript" -libraryDependencies <+= scalaVersion { v => - "org.scala-lang" % "scala-swing" % v -} - libraryDependencies <++= gtVersion { v => Seq( "org.geotools" % "gt-main" % v, @@ -17,10 +13,7 @@ libraryDependencies <++= gtVersion { v => ) } -libraryDependencies ++= - Seq( - "javax.media" % "jai_core" % "1.1.3", - "org.scala-tools.testing" %% "specs" % "[1.6,1.7)" % "test", - "org.scala-tools.testing" %% "specs" % "[1.6,1.7)" % "test", - "com.lowagie" % "itext" % "2.1.5" - ) +libraryDependencies ++= Seq( + "javax.media" % "jai_core" % "1.1.3", + "org.scala-tools.testing" %% "specs" % "[1.6,1.7)" % "test" +) diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 3bb0c53..989b91e 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -4,6 +4,7 @@ package object geoscript { import geometry._ import layer._ import workspace._ + import render._ implicit def enrichGeometry(g: Geometry): RichGeometry = new RichGeometry(g) @@ -20,11 +21,14 @@ package object geoscript { implicit def enrichSchema(s: Schema) = new RichSchema(s) - implicit def enrichLayer(layer: Layer): RichLayer = new RichLayer(layer) + implicit def enrichLayer(l: layer.Layer): RichLayer = new RichLayer(l) implicit def enrichWorkspace(workspace: Workspace): RichWorkspace = new RichWorkspace(workspace) implicit def enrichConnector(connector: Connector): RichConnector = new RichConnector(connector) + + implicit def enrichContent(content: Content): RichContent = + new RichContent(content) } diff --git a/geoscript/src/main/scala/geometry/io/package.scala b/geoscript/src/main/scala/geometry/io/package.scala index 532b2e3..bd9c06a 100644 --- a/geoscript/src/main/scala/geometry/io/package.scala +++ b/geoscript/src/main/scala/geometry/io/package.scala @@ -1,17 +1,6 @@ package org.geoscript.geometry -package io -import org.geoscript.io.{ Sink, Source } - -trait Reader[T] { - def read(source: Source): T -} - -trait Writer[T] { - def write[U](t: T, sink: Sink[U]): U -} - -trait Format[T] extends Writer[T] with Reader[T] +import org.geoscript.serialize._ object WKT extends Format[Geometry] { private val reader = new com.vividsolutions.jts.io.WKTReader() diff --git a/geoscript/src/main/scala/io/Sink.scala b/geoscript/src/main/scala/io/Sink.scala deleted file mode 100644 index f69c6b8..0000000 --- a/geoscript/src/main/scala/io/Sink.scala +++ /dev/null @@ -1,46 +0,0 @@ -package org.geoscript -package io - -import java.io.{ File, OutputStream } - -trait Sink[T] { - def apply(op: OutputStream => Unit): T -} - -object Sink { - implicit def stream(out: OutputStream): Sink[Unit] = - new Sink[Unit] { - def apply(op: OutputStream => Unit) = op(out) - } - - implicit def file(name: String): Sink[File] = - file(new java.io.File(name)) - - implicit def file(file: File): Sink[File] = - new Sink[java.io.File] { - def apply(op: OutputStream => Unit) = { - val output = - new java.io.BufferedOutputStream(new java.io.FileOutputStream(file)) - stream(output)(op) - output.close() - - file - } - } - - def string: Sink[String] = - new Sink[String] { // TODO: Needs better text support - def apply(op: OutputStream => Unit) = - buffer(op).view.map(_.toChar).mkString - } - - def buffer: Sink[Array[Byte]] = - new Sink[Array[Byte]] { - def apply(op: OutputStream => Unit) = { - val output = new java.io.ByteArrayOutputStream - stream(output)(op) - output.close() - output.toByteArray - } - } -} diff --git a/geoscript/src/main/scala/io/Source.scala b/geoscript/src/main/scala/io/Source.scala deleted file mode 100644 index 9976abd..0000000 --- a/geoscript/src/main/scala/io/Source.scala +++ /dev/null @@ -1,51 +0,0 @@ -package org.geoscript -package io - -import java.io.{ File, InputStream } - -trait Source { - def apply[T](op: InputStream => T): T -} - -object Source { - implicit def stream(in: InputStream): Source = - new Source { - def apply[T](op: InputStream => T): T = op(in) - } - - implicit def file(name: String): Source = - file(new java.io.File(name)) - - implicit def file(file: File): Source = - new Source { - def apply[T](op: InputStream => T): T = { - val input = - new java.io.BufferedInputStream(new java.io.FileInputStream(file)) - val res = stream(input)(op) - input.close() - - res - } - } - - def string(data: String): Source = - new Source { - def apply[T](op: InputStream => T): T = { - val input = new java.io.ByteArrayInputStream(data.getBytes()) - val res = stream(input)(op) - input.close() - res - } - } - - def buffer(data: Array[Byte]): Source = - new Source { - def apply[T](op: InputStream => T): T = { - val input = new java.io.ByteArrayInputStream(data) - val res = stream(input)(op) - input.close() - - res - } - } -} diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index cb639e3..944724f 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -9,35 +9,29 @@ import java.awt.RenderingHints, package render { trait Stylable[T] { - def applyStyle(t: T, s: style.Style): gt.map.Layer - def boundsOf(t: T): ReferencedEnvelope + def applyStyle(t: T, s: style.Style): Layer } object Stylable { + def stylable[T](f: (T, style.Style) => Layer): Stylable[T] = + new Stylable[T] { def applyStyle(t: T, s: style.Style): Layer = f(t, s) } + implicit val vectorDataIsStylable: Stylable[layer.Layer] = - new Stylable[layer.Layer] { - override def applyStyle(lyr: layer.Layer, s: style.Style) - : gt.map.Layer = - new gt.map.FeatureLayer(lyr, s.underlying, lyr.name) - - override def boundsOf(lyr: layer.Layer): ReferencedEnvelope = - new ReferencedEnvelope(lyr.envelope, lyr.schema.geometry.getCoordinateReferenceSystem) - } + stylable { (l, s) => new gt.map.FeatureLayer(l, s.underlying, l.name) } } trait Canvas[T] { self => - def render(size: (Int, Int), draw: awt.Graphics2D => Unit): T + def render(draw: Draw): T def map[U](f: T => U): Canvas[U] = new Canvas[U] { - def render(size: (Int, Int), draw: awt.Graphics2D => Unit): U = - f(self.render(size, draw)) + def render(draw: Draw): U = + f(self.render(draw)) } } class ImageCanvas extends Canvas[awt.image.BufferedImage] { - def render( - size: (Int, Int), draw: awt.Graphics2D => Unit - ): awt.image.BufferedImage = { + val size = (256, 256) // TODO: Make this a constructor argument + override def render(draw: Draw): awt.image.BufferedImage = { val (w, h) = size val img = new java.awt.image.BufferedImage(w, h, java.awt.image.BufferedImage.TYPE_INT_ARGB) @@ -46,63 +40,83 @@ package render { graphics.setColor(java.awt.Color.WHITE) graphics.fillRect(0, 0, w, h) - draw(graphics) + draw(graphics, size) graphics.dispose() img } } class Window extends Canvas[javax.swing.JFrame] { - private val component = new java.awt.Canvas { + private val component = new javax.swing.JComponent { + // createBufferStrategy(2) setBackground(java.awt.Color.WHITE) - override def paint(g: awt.Graphics) = - draw(g.asInstanceOf[awt.Graphics2D]) + override def paintComponent(g: awt.Graphics) = + draw(g.asInstanceOf[awt.Graphics2D], dimensionAsTuple(getSize())) } private val frame = new javax.swing.JFrame - frame.setResizable(false) + // frame.setResizable(false) frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE) frame.add(component) + frame.setSize(256, 256) - private var draw: awt.Graphics2D => Unit = { _ => } + private var draw: Draw = { (_, _) => } - def render( - size: (Int, Int), draw: awt.Graphics2D => Unit - ): javax.swing.JFrame = { - frame.setSize(size._1, size._2) + override def render(draw: Draw): javax.swing.JFrame = { this.draw = draw component.repaint() if (!frame.isVisible) frame.setVisible(true) frame } } + + object Content { + def empty = new Content + def apply[T : Stylable](t: T, s: style.Style): Content = + empty.withData(t, s) + } + + class RichContent(content: Content) { + def withLayer(l: Layer): Content = { + val newContent = new Content + newContent.layers.addAll(content.layers) + newContent.addLayer(l) + newContent + } + def withData[T : Stylable](data: T, s: style.Style): Content = + withLayer(applyStyle(data, s)) + } } package object render { - def draw[T, Out]( - t: T, - sty: style.Style, - bounds: Option[ReferencedEnvelope] = None, - size: (Int, Int) = (512, 512), + type Content = gt.map.MapContent + type Draw = (awt.Graphics2D, Dimension) => Unit + type Dimension = (Int, Int) + type Layer = gt.map.Layer + type StyleLayer = gt.map.Layer + type DirectLayer = gt.map.DirectLayer + + def applyStyle[T : Stylable](t: T, s: org.geoscript.style.Style): StyleLayer = + implicitly[Stylable[T]].applyStyle(t, s) + + def draw[Out]( + content: Content, + // layers: Seq[Layer], + bounds: ReferencedEnvelope, canvas: Canvas[Out] = new ImageCanvas - ) ( - implicit ev: Stylable[T] - ): Out - = { - val effectiveBounds = bounds.getOrElse(ev.boundsOf(t)) - - val content = new gt.map.MapContent - content.addLayer(ev.applyStyle(t, sty)) - + ): Out = { + // val content = new gt.map.MapContent + // layers.foreach { content.addLayer } + // val hints = renderHints(KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON) - val renderer = new gt.renderer.lite.StreamingRenderer() - renderer.setJava2DHints(hints) - renderer.setMapContent(content) - val (w, h) = size - canvas.render(size, { g => - renderer.paint(g, new java.awt.Rectangle(w, h), effectiveBounds) - }) - } + canvas.render { (graphics, screenArea) => + val renderer = new gt.renderer.lite.StreamingRenderer() + renderer.setJava2DHints(hints) + renderer.setMapContent(content) + renderer.paint(graphics, tupleAsRectangle(screenArea), bounds) + } + } + def file(f: String) = new java.io.File(f) def png(f: java.io.File): Canvas[java.io.File] = @@ -114,5 +128,9 @@ package object render { def png(f: String): Canvas[java.io.File] = png(file(f)) private def renderHints(kv: (RenderingHints.Key, Any)*) = - new RenderingHints(Map(kv: _*).asJava) + new RenderingHints(kv.toMap.asJava) + + def dimensionAsTuple(dim: awt.Dimension): (Int, Int) = (dim.width, dim.height) + def tupleAsRectangle(dim: (Int, Int)): awt.Rectangle = + new awt.Rectangle(0, 0, dim._1, dim._2) } diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 44a5168..ec9c4d4 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -91,12 +91,12 @@ class UsageTests extends Specification { mem.names.length must_== 1 - dummy += feature.Feature( + dummy += feature.fromAttributes( "name" -> "San Francisco", "geom" -> point(37.78, -122.42) ) - dummy += feature.Feature( + dummy += feature.fromAttributes( "name" -> "New York", "geom" -> point(40.47, -73.58) ) diff --git a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index 9ab2b96..5b26207 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -1,14 +1,14 @@ package org.geoscript package geometry -import org.geoscript.io.{ Sink, Source } +import org.geoscript.serialize.{ Sink, Source } import org.specs._ class SerializationSpec extends Specification { "JSON Serialization" should { "round-trip points" in { val p = point(100, 0) - val json = io.GeoJSON.write(p, Sink.string) + val json = GeoJSON.write(p, Sink.string) json must_== """{"type":"Point","coordinates":[100,0.0]}""" // io.GeoJSON.read(Source.string(json)) must_== p // TODO: Implement equality for geometries @@ -16,7 +16,7 @@ class SerializationSpec extends Specification { "round-trip linestrings" in { val ls = lineString(Seq((100, 0), (101, 1))) - io.GeoJSON.write(ls, Sink.string) must_== + GeoJSON.write(ls, Sink.string) must_== """{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""" } @@ -32,15 +32,15 @@ class SerializationSpec extends Specification { )) ) - io.GeoJSON.write(solid, Sink.string) must_== + GeoJSON.write(solid, Sink.string) must_== """{"type":"Polygon","coordinates":[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]]]}""" - io.GeoJSON.write(withHoles, Sink.string) must_== + GeoJSON.write(withHoles, Sink.string) must_== """{"type":"Polygon","coordinates":[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}""" } "round-trip a multipoint" in { val mp = multiPoint(Seq((100.0, 0.0), (101.0, 1.0))) - io.GeoJSON.write(mp, Sink.string) must_== + GeoJSON.write(mp, Sink.string) must_== """{"type":"MultiPoint","coordinates":[[100,0.0],[101,1]]}""" } @@ -50,7 +50,7 @@ class SerializationSpec extends Specification { Seq((102, 2), (103, 3)) )) - io.GeoJSON.write(mls, Sink.string) must_== + GeoJSON.write(mls, Sink.string) must_== """{"type":"MultiLineString","coordinates":[[[100,0.0],[101,1]],[[102,2],[103,3]]]}""" } @@ -68,7 +68,7 @@ class SerializationSpec extends Specification { ) )) - io.GeoJSON.write(mp, Sink.string) must_== + GeoJSON.write(mp, Sink.string) must_== """{"type":"MultiPolygon","coordinates":[[[[102,2],[103,2],[103,3],[102,3],[102,2]]],[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]]}""" } @@ -78,7 +78,7 @@ class SerializationSpec extends Specification { lineString(Seq((101.0, 0.0), (102.0, 1.0))) )) - io.GeoJSON.write(gc, Sink.string) must_== + GeoJSON.write(gc, Sink.string) must_== """{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[100,0.0]},{"type":"LineString","coordinates":[[101,0.0],[102,1]]}]}""" } } diff --git a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index 6ccbfb1..15bd37d 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -13,7 +13,7 @@ class MemorySpec extends Specification { bind[String]("name"))) workspace.withMemoryWorkspace { ws => val lyr = ws.create(schema) - lyr += feature.Feature( + lyr += feature.fromAttributes( "the_geom" -> geometry.point(0, 0), "name" -> "test" ) From d5a67f3fe0377428d20680d8ac74bbe71649cfa4 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 3 Jun 2012 20:40:01 -0400 Subject: [PATCH 23/53] Spruce up sphinx documentation a bit --- geoscript/src/main/sphinx/download.rst | 131 ++++++++++++------ geoscript/src/main/sphinx/index.rst | 6 + geoscript/src/main/sphinx/quickstart.rst | 80 ++++++----- .../src/main/sphinx/sample_maven_pom.xml | 97 +++++++++++++ .../src/main/sphinx/sample_sbt_build.sbt | 12 ++ 5 files changed, 247 insertions(+), 79 deletions(-) create mode 100644 geoscript/src/main/sphinx/sample_maven_pom.xml create mode 100644 geoscript/src/main/sphinx/sample_sbt_build.sbt diff --git a/geoscript/src/main/sphinx/download.rst b/geoscript/src/main/sphinx/download.rst index 5ec931a..f3c8381 100644 --- a/geoscript/src/main/sphinx/download.rst +++ b/geoscript/src/main/sphinx/download.rst @@ -1,67 +1,110 @@ -.. highlight:: sh +.. highlight:: scala -Quick Start -=========== +Getting GeoScript.scala +======================= -GeoScript.scala provides a streamlined Scala API for geospatial data access and -operations. Internally, it uses the `GeoTools library -`_. +This guide will help you get up and running with GeoScript Scala. +Unlike some other GeoScript variants, the Scala variant does not ship a pre-packaged bundle with all dependencies in one download. +Instead, we recommend using one of the build tools listed below for trying out GeoScript Scala; both provide an interactive interpreter along with the ability to compile and run sources. -In this quickstart, we will cover the following: +Project Template +================ -#. Build GeoScript.scala from sources -#. Use GeoScript in a Scala interpreter +A project template is available for download from Github. +If you are a Git user, you can use the following command to clone the template locally:: + $ git clone git://github.com/dwins/geoscript-project-template.git -Install sbt +Alternatively, visit https://github.com/dwins/geoscript-project-template/ to download the template project in ZIP or TAR.GZ format. + +This template includes SBT, which will download GeoScript and its dependencies the first time it is run. +After extracting the archive, use a terminal to change directories into the template project and use:: + + ./sbt console + +to fetch GeoScript Scala and launch a Scala console. +Read on for more information about using SBT. + +SBT +=== + +SBT is a popular build tool for Scala projects. +SBT build configurations can be written in pure Scala, or in "build.sbt" files which are briefer but less customizable. + +Getting SBT ----------- +Instructions for getting SBT are available at http://scala-sbt.org/ + +Configuration +------------- + +A simple build configuration using GeoScript Scala follows: + +.. literalinclude:: sample_sbt_build.sbt + :language: scala + +Commands +-------- + +Some common commands that may be useful when working with GeoScript in SBT. + +* ``sbt console`` launches a Scala :abbr:`REPL` or interactive shell with the GeoScript library loaded. + +* ``sbt run`` finds an executable object in your project's sources and executes it. + Command-line arguments may be provided. + If you have multiple executable objects this command will prompt to let you choose which to run; for repeated runs, it's often more convenient to use ``sbt run-main qualified.name.of.Executable``. + +* ``sbt test`` runs unit tests for your project. + You should load `Specs2 `_ or `ScalaTest `_ to write your tests. + `JUnit `_ is also supported through the `junit-interface `_ plugin. + +* Any SBT command can be prefixed with a tilde (``~``) to be re-run automatically when source files are modified. + For example, if you leave ``sbt ~test`` running while you edit your sources, you'll be notified of test failures immediately after making the changes that introduce them. + +* If you run ``sbt`` with no arguments it will enter its own interactive console, which provides tab-completion. + This is also useful to keep the JVM "warmed-up" between running different SBT commands. + +* For more information, see the SBT wiki at https://github.com/harrah/xsbt/wiki/ . + +Maven +===== -Download ``sbt`` from the project’s `website -`_. +Maven is a popular build tool for Java projects and can also be used with Scala. +Configuration +------------- -At the time of this writing a complied jar is available:: - - $ wget http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.1.jar +You can download a :download:`sample POM ` for a GeoScript Maven project. +This configuration uses the `Maven Scala plugin `_ as well as loading the GeoScript library and its dependencies. -Create a script called ``sbt``:: +Commands +-------- - $ echo "java -Xmx512M -jar `dirname $0`/sbt-launch-0.7.1.jar "$@"" >> sbt - -Make that script executable:: - - $ chmod u+x sbt +The following are some common commands that may be useful when working with GeoScript in Maven. +* ``mvn scala:cc`` watches the source directory and re-runs the compiler when files change; handy to see compile errors immediately after introducing them. -Set Up GeoScript Scala ----------------------- +* ``mvn scala:console`` launches an interactive Scala interpreter with your project and all dependencies loaded. -Download GeoScript.scala:: +* See http://maven.apache.org/ and http://scala-tools.org/mvnsites/maven-scala-plugin/ for more information. - $ git clone git://github.com/dwins/geoscript.scala.git - -Start ``sbt``:: +Others +====== - $ cd geoscript.scala/ - $ sbt +Using GeoScript Scala is, in theory, possible with any build tool that supports the Scala compiler and Maven repositories for fetching dependencies. +If you want to do so, here is the information. -.. highlight:: none +Repositories +------------ -Fetch the dependencies, build GeoScript, and run the test suite:: - - > update - > compile - > test - -Explore ``sbt``:: +GeoScript Scala itself is published in the OpenGeo maven repository at http://repo.opengeo.org/ - > actions +It requires dependencies that are hosted in the OSGeo Maven repository at http://download.osgeo.org/webdav/geotools/ -Start the Scala command line interpreter:: +Artifacts +--------- - > console - scala> import org.geoscript._ - scala> import GeoScript._ +GeoScript Scala publishes two artifacts: -For more information check out the ``sbt`` `Google project page -`_. +* ``org.geoscript:geoscript_2.9.1:geocss`` implements support for a CSS-like language that is translated to GeoTools (and therefore GeoScript) Style objects. +* ``org.geoscript.geoscript_2.9.1:geoscript`` is the GeoScript library itself. diff --git a/geoscript/src/main/sphinx/index.rst b/geoscript/src/main/sphinx/index.rst index 0e62d17..32eb0b3 100644 --- a/geoscript/src/main/sphinx/index.rst +++ b/geoscript/src/main/sphinx/index.rst @@ -9,11 +9,17 @@ GeoScript Scala .. toctree:: :hidden: + download quickstart introduction + tutorial + style GeoScript Scala is a GeoScript implementation for the Scala programming language. + :doc:`Download ` + Get GeoScript on your computer + :doc:`Quickstart ` Get up and running with GeoScript diff --git a/geoscript/src/main/sphinx/quickstart.rst b/geoscript/src/main/sphinx/quickstart.rst index 3971be2..369f384 100644 --- a/geoscript/src/main/sphinx/quickstart.rst +++ b/geoscript/src/main/sphinx/quickstart.rst @@ -1,57 +1,67 @@ -.. highlight:: sh - Quick Start =========== This guide will help you get up and running with GeoScript Scala. +We assume you are using `SBT `_ to build your project. +See :doc:`download` for alternative setup information. + +Set up a project +---------------- -Java Virtual Machine --------------------- +After installing SBT, you'll need to setup your project. +An SBT project is just a directory that contains an SBT build configuration (and whatever source code you have, but we'll get to that shortly.) +Usually, it's a good idea to start a project by creating a directory, then changing directories into it: -GeoScript Scala requires the Java Virtual Machine in order to operate. Most systems have Java preinstalled; you can verify this with the command:: +.. code-block: sh - $ java -version - java version "1.5.0_20" - Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_20-b02-315) - Java HotSpot(TM) Client VM (build 1.5.0_20-141, mixed mode, sharing) + $ mkdir my-first-geoscript + $ cd my-first-geoscript -If your Java version is ``1.5`` or greater, you are ready to install GeoScript -Scala. Otherwise, we recommend installing Sun's JVM, available from `the Sun -website `_. +Next, create a ``built.sbt`` file containing the configuration for your project. +A minimal configuration follows: +.. literalinclude:: sample.build.sbt + :language: scala -Installing GeoScript --------------------- +This file belongs in the root of the project directory; that is, if your project directory is :file:`hello-geoscript` then the build configuration should be stored in :file:`hello-geoscript/build.sbt`. -You can download an archive of the latest GeoScript Scala release at the -`GitHub download page`_. Unpack the archive to your filesystem:: +Try it +------ - $ tar xvzf geoscript-0.1.tar.gz +Now you can verify that GeoScript is available by using the interactive Scala interpreter, or REPL (short for "Read Eval Print Loop.") +To do so, use sbt: -After unpacking, you can run the GeoScript interpreter by specifying the path:: +.. code-block: sh - $ geoscript-0.1/bin/geoscript-scala - Welcome to Scala version 2.7.7.final (HotSpot(TM) Client VM, Java 1.5.0_20-141) + $ sbt console + [info] Loading project definition from /home/dwins/hello-geoscript/project + [info] Set current project to default-79d942 (in build file:/home/dwins/hello-geoscript/) + [info] Starting scala interpreter... + [info] + Welcome to Scala version 2.9.1.final (Java HotSpot(TM) Server VM, Java 1.6.0_30). Type in expressions to have them evaluated. Type :help for more information. - scala> + scala> + +With the console, you can type in Scala code and see the results immediately. +Let's try it out by buffering a simple point geometry: + +.. code-block: scala -Adding to the PATH ------------------- + scala> import org.geoscript._ + import org.geoscript._ -In order to avoid needing to provide the path every time you want to use -GeoScript, you can add GeoScript to your shell's PATH variable. On Mac OSX, -Linux, and other Unixy systems, you can do this:: + scala> geometry.point(1,2) + res0: org.geoscript.geometry.package.Point = POINT (1 2) - $ echo 'export PATH=/path/to/geoscript/:$PATH' >> ~/.profile - $ . ~/.profile # apply changes, otherwise they won't apply until next login + scala> res0.buffer(0.2) + res1: com.vividsolutions.jts.geom.Geometry = POLYGON ((1.2 2, 1.196157056080646 1.9609819355967744, 1.1847759065022574 1.923463313526982, 1.1662939224605091 1.8888859533960796, 1.1414213562373094 1.8585786437626906, 1.1111140466039204 1.8337060775394909, 1.076536686473018 1.8152240934977426, 1.0390180644032256 1.803842943919354, 1 1.8, 0.9609819355967744 1.803842943919354, 0.9234633135269821 1.8152240934977426, 0.8888859533960796 1.8337060775394909, 0.8585786437626906 1.8585786437626906, 0.8337060775394909 1.8888859533960796, 0.8152240934977426 1.923463313526982, 0.8038429439193538 1.9609819355967744, 0.8 2, 0.803842943919354 2.039018064403226, 0.8152240934977427 2.0765366864730184, 0.8337060775394911 2.111114046603921, 0.8585786437626908 2.1414213562373097, 0.8888859533960798 2.1662939... + scala> res1.getArea + res2: Double = 0.12485780609032211 -On Windows, you can do this by editing your PATH environment variable through -the Properties dialog for My Computer. +What's next +----------- -.. Other Options - ------------- - - You can also :doc:`build GeoScript Scala from sources` or - :doc:`include` it in a managed project. +From here, you can view the ``documentation <../learning`` to learn more about what GeoScript Scala can do, or the ``reference `` for the GeoScript Scala API. +If you're new to Scala, there's a quick ``Scala tutorial `` as well as the community documenation at http://docs.scala-lang.org/ . diff --git a/geoscript/src/main/sphinx/sample_maven_pom.xml b/geoscript/src/main/sphinx/sample_maven_pom.xml new file mode 100644 index 0000000..defb6cc --- /dev/null +++ b/geoscript/src/main/sphinx/sample_maven_pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + org.example + example + jar + 1.0-SNAPSHOT + Example GeoScript Scala Project + + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + osgeo + OSGeo Repository + http://download.osgeo.org/webdav/geotools/ + + + opengeo + OpenGeo Repository + http://repo.opengeo.org/ + + + + + scala-tools.org + Scala-Tools Maven2 Repository + http://scala-tools.org/repo-releases + + + + + + org.scala-lang + scala-library + ${scala.version} + + + org.scala-lang + scala-compiler + ${scala.version} + provided + + + org.geoscript + geoscript_${scala.version} + 0.7.3 + + + + 2.9.1 + true + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.4 + + UTF-8 + + + + org.scala-tools + maven-scala-plugin + + + scala-compile-first + process-resources + + add-source + compile + + + + scala-test-compile + process-test-resources + + testCompile + + + + + + + + ${basedir}/src/test/resources/ + + **/* + + + + + diff --git a/geoscript/src/main/sphinx/sample_sbt_build.sbt b/geoscript/src/main/sphinx/sample_sbt_build.sbt new file mode 100644 index 0000000..4d0c444 --- /dev/null +++ b/geoscript/src/main/sphinx/sample_sbt_build.sbt @@ -0,0 +1,12 @@ +// Enable OSGeo repository for GeoTools and its dependencies +resolvers += "osgeo" at "http://download.osgeo.org/webdav/geotools/" + +// Enable OpenGeo repository for GeoScript itself +resolvers += "opengeo" at "http://repo.opengeo.org/" + +// list GeoScript as a dependency for the project +libraryDependencies += + "org.geoscript" %% "geoscript" % "0.7.3" + +// Currently GeoScript is only built for Scala version 2.9.1 +scalaVersion := "2.9.1" From c33546579e15d1de59443d4f2e5c13385bb70f00 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 3 Jun 2012 20:40:26 -0400 Subject: [PATCH 24/53] Add missing 'serialize' package --- .../src/main/scala/serialize/package.scala | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 geoscript/src/main/scala/serialize/package.scala diff --git a/geoscript/src/main/scala/serialize/package.scala b/geoscript/src/main/scala/serialize/package.scala new file mode 100644 index 0000000..d3cade7 --- /dev/null +++ b/geoscript/src/main/scala/serialize/package.scala @@ -0,0 +1,103 @@ +package org.geoscript +package serialize + +import java.io.{ File, InputStream, OutputStream } + +trait Reader[+T] { + def read(source: Source): T +} + +trait Writer[-T] { + def write[A](t: T, sink: Sink[A]): A +} + +trait Format[T] extends Reader[T] with Writer[T] + +trait Sink[T] { + def apply(op: OutputStream => Unit): T +} + +object Sink { + implicit def stream(out: OutputStream): Sink[Unit] = + new Sink[Unit] { + def apply(op: OutputStream => Unit) = op(out) + } + + implicit def file(name: String): Sink[File] = + file(new java.io.File(name)) + + implicit def file(file: File): Sink[File] = + new Sink[java.io.File] { + def apply(op: OutputStream => Unit) = { + val output = + new java.io.BufferedOutputStream(new java.io.FileOutputStream(file)) + stream(output)(op) + output.close() + + file + } + } + + def string: Sink[String] = + new Sink[String] { // TODO: Needs better text support + def apply(op: OutputStream => Unit) = + buffer(op).view.map(_.toChar).mkString + } + + def buffer: Sink[Array[Byte]] = + new Sink[Array[Byte]] { + def apply(op: OutputStream => Unit) = { + val output = new java.io.ByteArrayOutputStream + stream(output)(op) + output.close() + output.toByteArray + } + } +} + +trait Source { + def apply[T](op: InputStream => T): T +} + +object Source { + implicit def stream(in: InputStream): Source = + new Source { + def apply[T](op: InputStream => T): T = op(in) + } + + implicit def file(name: String): Source = + file(new java.io.File(name)) + + implicit def file(file: File): Source = + new Source { + def apply[T](op: InputStream => T): T = { + val input = + new java.io.BufferedInputStream(new java.io.FileInputStream(file)) + val res = stream(input)(op) + input.close() + + res + } + } + + def string(data: String): Source = + new Source { + def apply[T](op: InputStream => T): T = { + val input = new java.io.ByteArrayInputStream(data.getBytes()) + val res = stream(input)(op) + input.close() + res + } + } + + def buffer(data: Array[Byte]): Source = + new Source { + def apply[T](op: InputStream => T): T = { + val input = new java.io.ByteArrayInputStream(data) + val res = stream(input)(op) + input.close() + + res + } + } +} From 33ffce19e67c7cbd47601f169a14f5fa6cc3860b Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 3 Jun 2012 20:40:57 -0400 Subject: [PATCH 25/53] Add Referenced class to projection package --- .../main/scala/projection/Referenced.scala | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 geoscript/src/main/scala/projection/Referenced.scala diff --git a/geoscript/src/main/scala/projection/Referenced.scala b/geoscript/src/main/scala/projection/Referenced.scala new file mode 100644 index 0000000..0a27295 --- /dev/null +++ b/geoscript/src/main/scala/projection/Referenced.scala @@ -0,0 +1,54 @@ +package org.geoscript +package projection + +trait Referenced[T] { + def map[U](f: T => U): Referenced[U] + def flatMap[U](f: T => Referenced[U]): Referenced[U] + def force(p: Projection): T +} + +object Referenced { + def apply[T <: geometry.Geometry](value: T, projection: Projection) + : Referenced[T] = new Physical(value, projection) + + def apply[T](value: T): Referenced[T] = new Ideal(value) + + private class Ideal[T](value: T) extends Referenced[T] { + def map[U](f: T => U): Referenced[U] = new Ideal(f(value)) + def flatMap[U](f: T => Referenced[U]): Referenced[U] = f(value) + def force(p: Projection): T = value + } + + private class Physical[T <: geometry.Geometry](value: T, proj: Projection) + extends Referenced[T] { + def map[U](f: T => U): Referenced[U] = + new Mapped(this, f) + + def flatMap[U](f: T => Referenced[U]): Referenced[U] = + new FlatMapped(this, f) + + def force(p: Projection): T = + (proj to p)(value) + } + + private class Mapped[T, U](base: Referenced[T], f: T => U) extends Referenced[U] { + def map[V](g: U => V): Referenced[V] = + new Mapped(base, f andThen g) + + def flatMap[V](g: U => Referenced[V]): Referenced[V] = + new FlatMapped(base, f andThen g) + + def force(p: Projection): U = f(base.force(p)) + } + + private class FlatMapped[T, U](base: Referenced[T], f: T => Referenced[U]) + extends Referenced[U] { + def map[V](g: U => V): Referenced[V] = new Mapped(this, g) + + def flatMap[V](g: U => Referenced[V]): Referenced[V] = + new FlatMapped(this, g) + + def force(p: Projection): U = + f(base.force(p)).force(p) + } +} From 6adfe3ce4e587bccb6031e637d2ce2e4eb1b78c9 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 3 Jun 2012 21:10:06 -0400 Subject: [PATCH 26/53] Tweaks to the docs --- geoscript/src/main/sphinx/index.rst | 5 --- geoscript/src/main/sphinx/quickstart.rst | 47 +++++++++++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/geoscript/src/main/sphinx/index.rst b/geoscript/src/main/sphinx/index.rst index 32eb0b3..e712c70 100644 --- a/geoscript/src/main/sphinx/index.rst +++ b/geoscript/src/main/sphinx/index.rst @@ -1,8 +1,3 @@ -.. GeoScript.scala documentation master file, created by - sphinx-quickstart on Sat Jan 23 16:29:57 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - GeoScript Scala =============== diff --git a/geoscript/src/main/sphinx/quickstart.rst b/geoscript/src/main/sphinx/quickstart.rst index 369f384..1865ad6 100644 --- a/geoscript/src/main/sphinx/quickstart.rst +++ b/geoscript/src/main/sphinx/quickstart.rst @@ -20,7 +20,7 @@ Usually, it's a good idea to start a project by creating a directory, then chang Next, create a ``built.sbt`` file containing the configuration for your project. A minimal configuration follows: -.. literalinclude:: sample.build.sbt +.. literalinclude:: sample_sbt_build.sbt :language: scala This file belongs in the root of the project directory; that is, if your project directory is :file:`hello-geoscript` then the build configuration should be stored in :file:`hello-geoscript/build.sbt`. @@ -31,7 +31,7 @@ Try it Now you can verify that GeoScript is available by using the interactive Scala interpreter, or REPL (short for "Read Eval Print Loop.") To do so, use sbt: -.. code-block: sh +.. code-block:: sh $ sbt console [info] Loading project definition from /home/dwins/hello-geoscript/project @@ -47,7 +47,7 @@ To do so, use sbt: With the console, you can type in Scala code and see the results immediately. Let's try it out by buffering a simple point geometry: -.. code-block: scala +.. code-block:: scala scala> import org.geoscript._ import org.geoscript._ @@ -60,8 +60,45 @@ Let's try it out by buffering a simple point geometry: scala> res1.getArea res2: Double = 0.12485780609032211 +Coding +------ + +While the REPL is handy for experimenting, often when writing code with GeoScript Scala it's useful to store the code in a file for later reuse. + +Let's do the same calculation from above as an executable program. +First, create a source directory. +SBT will look for Scala sources in :file:`src/main/scala/`, so we should create that and any necessary parent directories:: + + $ mkdir -p src/main/scala/ + +Next, create a HelloGeoScript.scala file in that directory. +You can use the App trait from the Scala standard library to easily make an executable. + +.. code-block:: scala + + import org.geoscript._ + object HelloGeoScript extends App { + val p1 = geometry.point(1, 2) + val p2 = p1.buffer(0.2) + println("Area of buffered point is: " + p2.getArea) + } + +.. note:: + + In compiled code, Scala won't automatically print all calculation results for us. + We have to explicitly use the ``println`` function to display values that we are interested in. + +Now, instead of using SBT's ``console`` command, use ``run`` to run the code:: + + $ sbt run + [info] Loading project definition from /home/dwins/hello-geoscript/project + [info] Set current project to default-79d942 (in build file:/home/dwins/hello-geoscript/) + [info] Running HelloGeoScript + Area of buffered point is: 0.12485780609032211 + [success] Total time: 4s, completed Jun 3, 2012, 9:01:59 PM + What's next ----------- -From here, you can view the ``documentation <../learning`` to learn more about what GeoScript Scala can do, or the ``reference `` for the GeoScript Scala API. -If you're new to Scala, there's a quick ``Scala tutorial `` as well as the community documenation at http://docs.scala-lang.org/ . +From here, you can view the `documentation <../learning>`_ to learn more about what GeoScript Scala can do, or the `reference `_ for the GeoScript Scala API. +If you're new to Scala, there's a quick `Scala tutorial `_ as well as the community documenation at http://docs.scala-lang.org/ . From c440ea133cf0c6f62f8d6b0ef9a23a0b1005a0ba Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sat, 9 Jun 2012 19:03:02 -0400 Subject: [PATCH 27/53] More API tweaks --- examples/src/main/scala/example/Postgis.scala | 3 +- examples/src/main/scala/example/Shp2Shp.scala | 3 +- .../main/scala/projection/Projection.scala | 88 +++---------------- .../main/scala/projection/Referenced.scala | 2 +- geoscript/src/test/scala/UsageTests.scala | 4 +- .../org/geoscript/workspaces/MemorySpec.scala | 4 +- project/Build.scala | 3 +- 7 files changed, 24 insertions(+), 83 deletions(-) diff --git a/examples/src/main/scala/example/Postgis.scala b/examples/src/main/scala/example/Postgis.scala index a3e963d..4722803 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -3,6 +3,7 @@ package org.geoscript.example import com.vividsolutions.jts.geom.Geometry import org.geoscript._ import feature.{ Feature, Field, Schema, bind } +import projection.Projection object PostgisTest extends App { val params = workspace.Params.postgis("conflict") @@ -15,7 +16,7 @@ object PostgisTest extends App { workspace.withWorkspace(workspace.Params.postgis("test")) { wsTest => val test = wsTest.create(Schema( "test", - Seq(bind[String]("name"), bind[Geometry]("geom", "EPSG:4326")))) + Seq(bind[String]("name"), bind[Geometry]("geom", Projection("EPSG:4326"))))) test += feature.fromAttributes( "name" -> "test", diff --git a/examples/src/main/scala/example/Shp2Shp.scala b/examples/src/main/scala/example/Shp2Shp.scala index f9ec98a..c1f272a 100644 --- a/examples/src/main/scala/example/Shp2Shp.scala +++ b/examples/src/main/scala/example/Shp2Shp.scala @@ -2,13 +2,14 @@ package org.geoscript.example import org.geoscript._ import feature.{ Field, GeoField, Schema } +import projection.Projection object Shp2Shp extends App { val Array(sourcefile, destname, proj) = args take 3 val source = layer.Shapefile(sourcefile) val destSchema = Schema(destname, source.schema.fields map { - case (g: GeoField) => g.withProjection(proj) + case (g: GeoField) => g.withProjection(Projection(proj)) case (f: Field) => f } ) diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index 4981b0b..efdba8d 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -1,4 +1,4 @@ -package org.geoscript.projection +package org.geoscript import com.vividsolutions.jts.{geom => jts} @@ -9,82 +9,18 @@ import org.geotools.factory.Hints import org.geotools.geometry.jts.JTS import org.geotools.referencing.CRS -/** - * A Projection is a particular system of representing real-world locations - * with numeric coordinates. For example, Projection("EPSG:4326") is the - * system of representing positions with (longitude, latitude) pairs. - */ -class Projection(val crs: CoordinateReferenceSystem) { - /** - * Create a conversion function from this projection to another one, which - * can be applied to JTS Geometries. Example usage: - * - *
 
-   * val jtsGeom = Point(1, 2).underlying
-   * val convert: jts.Point => jts.Point = Projection("EPSG:4326") to Projection("EPSG:3785")
-   * val reprojected = convert(jtsGeom)
-   * 
- */ - def to[Geom<:jts.Geometry](dest: Projection)(geom: Geom) = { - val tx = CRS.findMathTransform(crs, dest.crs) - JTS.transform(geom, tx).asInstanceOf[Geom] - } +package object projection { + type Projection = CoordinateReferenceSystem - /** - * Get the official spatial reference identifier for this projection, if any - */ - def id: String = CRS.toSRS(crs) + def fromWKT(s: String): Option[Projection] = Some(CRS.parseWKT(s)) + def fromSrid(s: String): Option[Projection] = Some(CRS.decode(s)) + def transform[G <: geometry.Geometry] + (p: Projection, q: Projection) + (g: G) + : G = sys.error("Unimplemented") - /** - * Get the Well Known Text specification of this projection. - * - * @see http://en.wikipedia.org/wiki/Well-known_text - */ - def wkt: String = crs.toString() + def Projection(s: String): Projection = (fromSrid(s) orElse fromWKT(s)).orNull - override def toString: String = - id match { - case null => "" - case id => id - } -} - -/** - * The Projection object provides several methods for getting Projection - * instances. - */ -object Projection { - val forceXY = System.getProperty("org.geotools.referencing.forceXY") - - if (forceXY == null || forceXY.toBoolean == false) - System.setProperty("org.geotools.referencing.forceXY", "true") - - if (Hints.getSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER) - .asInstanceOf[Boolean]) - Hints.putSystemDefault(Hints.FORCE_AXIS_ORDER_HONORING, "http") - - /** - * Get a Projection instance by either looking up an identifier in the - * projection database, or decoding a Well-Known Text definition. - * - * @see http://en.wikipedia.org/wiki/Well-known_text - */ - def apply(s: String): Projection = - new Projection( - try { - CRS.decode(s) - } catch { - case _ => CRS.parseWKT(s) - } - ) - - /** - * Create a Projection by wrapping a GeoTools CoordinateReferenceSystem - * object. - */ - def apply(crs: CoordinateReferenceSystem) = new Projection(crs) - - implicit def codeToCRS(code: String) = Projection(code) - implicit def wrapCRS(crs: CoordinateReferenceSystem) = Projection(crs) - implicit def unwrapCRS(proj: Projection) = proj.crs + lazy val LatLon = fromSrid("EPSG:4326") + lazy val WebMercator = fromSrid("EPSG:3857") } diff --git a/geoscript/src/main/scala/projection/Referenced.scala b/geoscript/src/main/scala/projection/Referenced.scala index 0a27295..612dfa4 100644 --- a/geoscript/src/main/scala/projection/Referenced.scala +++ b/geoscript/src/main/scala/projection/Referenced.scala @@ -28,7 +28,7 @@ object Referenced { new FlatMapped(this, f) def force(p: Projection): T = - (proj to p)(value) + (org.geoscript.projection.transform(proj, p)(value)) } private class Mapped[T, U](base: Referenced[T], f: T => U) extends Referenced[U] { diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index ec9c4d4..77d7af6 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -9,7 +9,7 @@ class UsageTests extends Specification { "geometries" should { "work like on the geoscript homepage" in { var p = point(-111, 45.7) - var p2 = (Projection("epsg:4326") to Projection("epsg:26912"))(p) + var p2 = (projection.transform(Projection("epsg:4326"), Projection("epsg:26912")))(p) var poly = p.buffer(100) p2.x must beCloseTo(499999.0, 1) @@ -87,7 +87,7 @@ class UsageTests extends Specification { withMemoryWorkspace { mem => mem.names must beEmpty var dummy = mem.create(Schema("dummy", - Seq(bind[String]("name"), bind[Geometry]("geom", "EPSG:4326")))) + Seq(bind[String]("name"), bind[Geometry]("geom", Projection("EPSG:4326"))))) mem.names.length must_== 1 diff --git a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index 15bd37d..e3c5357 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -1,6 +1,8 @@ package org.geoscript package workspace +import projection.Projection + import org.specs._ class MemorySpec extends Specification { @@ -9,7 +11,7 @@ class MemorySpec extends Specification { "be able to create layers" in { val schema = Schema("cities", Seq( - bind[geometry.Point]("the_geom", "EPSG:4326"), + bind[geometry.Point]("the_geom", Projection("EPSG:4326")), bind[String]("name"))) workspace.withMemoryWorkspace { ws => val lyr = ws.create(schema) diff --git a/project/Build.scala b/project/Build.scala index 92b52d4..808551a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,7 +10,8 @@ object GeoScript extends Build { version := "0.8.0", gtVersion := "8.0-M4", scalaVersion := "2.9.1", - scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked") + scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked"), + checksums := Nil ) val common = From fa083c39826c31487a64cf07129e918ca5cac1ee Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sat, 9 Jun 2012 19:04:24 -0400 Subject: [PATCH 28/53] Get rid of old "assembly" configuration --- .../src/main/assembly/bin/geoscript-scala | 66 ------------- .../src/main/assembly/bin/geoscript-scala.bat | 93 ------------------- geoscript/src/main/assembly/doc/SCALA-LICENSE | 35 ------- geoscript/src/main/assembly/doc/SCALA-README | 63 ------------- 4 files changed, 257 deletions(-) delete mode 100755 geoscript/src/main/assembly/bin/geoscript-scala delete mode 100644 geoscript/src/main/assembly/bin/geoscript-scala.bat delete mode 100644 geoscript/src/main/assembly/doc/SCALA-LICENSE delete mode 100644 geoscript/src/main/assembly/doc/SCALA-README diff --git a/geoscript/src/main/assembly/bin/geoscript-scala b/geoscript/src/main/assembly/bin/geoscript-scala deleted file mode 100755 index 6b62293..0000000 --- a/geoscript/src/main/assembly/bin/geoscript-scala +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh - -############################################################################## -# Copyright 2002-2009, LAMP/EPFL -# -# This is free software; see the distribution for copying conditions. -# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. -############################################################################## - -cygwin=false; -case "`uname`" in - CYGWIN*) cygwin=true ;; -esac - -# Finding the root folder for this Scala distribution -SOURCE=$0; -SCRIPT=`basename "$SOURCE"`; -while [ -h "$SOURCE" ]; do - SCRIPT=`basename "$SOURCE"`; - LOOKUP=`ls -ld "$SOURCE"`; - TARGET=`expr "$LOOKUP" : '.*-> \(.*\)$'`; - if expr "${TARGET:-.}/" : '/.*/$' > /dev/null; then - SOURCE=${TARGET:-.}; - else - SOURCE=`dirname "$SOURCE"`/${TARGET:-.}; - fi; -done; -SCALA_HOME=`dirname "$SOURCE"`/..; -SCALA_HOME=`cd "$SCALA_HOME"; pwd`; -# Remove spaces from SCALA_HOME on windows -if $cygwin; then - SCALA_HOME=`cygpath --windows --short-name "$SCALA_HOME"` - SCALA_HOME=`cygpath --unix "$SCALA_HOME"` -fi - -# Constructing the extension classpath -TOOL_CLASSPATH="" -if [ -z "$TOOL_CLASSPATH" ] ; then - for ext in `ls -d "$SCALA_HOME"/lib/*` ; do - if [ -z "$TOOL_CLASSPATH" ] ; then - TOOL_CLASSPATH="$ext" - else - TOOL_CLASSPATH="$TOOL_CLASSPATH:$ext" - fi - done -fi - -if $cygwin; then - if [ "$OS" = "Windows_NT" ] && cygpath -m .>/dev/null 2>/dev/null ; then - format=mixed - else - format=windows - fi - SCALA_HOME=`cygpath --$format "$SCALA_HOME"` - TOOL_CLASSPATH=`cygpath --path --$format "$TOOL_CLASSPATH"` -fi - -# Reminder: substitution ${JAVA_OPTS:=-Xmx256M -Xms16M} DO NOT work on Solaris -[ -n "$JAVA_OPTS" ] || JAVA_OPTS="-Xmx256M -Xms32M" - -if [ -z "$JAVACMD" -a -n "$JAVA_HOME" -a -x "$JAVA_HOME/bin/java" ]; then - JAVACMD="$JAVA_HOME/bin/java" -fi - -exec "${JAVACMD:=java}" $JAVA_OPTS -cp "$TOOL_CLASSPATH" -Dscala.home="$SCALA_HOME" -Denv.classpath="$CLASSPATH" -Denv.emacs="$EMACS" scala.tools.nsc.MainGenericRunner "$@" diff --git a/geoscript/src/main/assembly/bin/geoscript-scala.bat b/geoscript/src/main/assembly/bin/geoscript-scala.bat deleted file mode 100644 index 69ae9dc..0000000 --- a/geoscript/src/main/assembly/bin/geoscript-scala.bat +++ /dev/null @@ -1,93 +0,0 @@ -@echo off - -rem ########################################################################## -rem # Copyright 2002-2009, LAMP/EPFL -rem # -rem # This is free software; see the distribution for copying conditions. -rem # There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A -rem # PARTICULAR PURPOSE. -rem ########################################################################## - -rem We adopt the following conventions: -rem - System/user environment variables start with a letter -rem - Local batch variables start with an underscore ('_') - -if "%OS%"=="Windows_NT" ( - @setlocal - call :set_home - set _ARGS=%* -) else ( - set _SCALA_HOME=%SCALA_HOME% - rem The following line tests SCALA_HOME instead of _SCALA_HOME, because - rem the above change to _SCALA_HOME is not visible within this block. - if "%SCALA_HOME%"=="" goto error1 - call :set_args -) - -rem We use the value of the JAVACMD environment variable if defined -set _JAVACMD=%JAVACMD% - -if "%_JAVACMD%"=="" ( - if not "%JAVA_HOME%"=="" ( - if exist "%JAVA_HOME%\bin\java.exe" set _JAVACMD=%JAVA_HOME%\bin\java.exe - ) -) - -if "%_JAVACMD%"=="" set _JAVACMD=java - -rem We use the value of the JAVA_OPTS environment variable if defined -set _JAVA_OPTS=%JAVA_OPTS% -if "%_JAVA_OPTS%"=="" set _JAVA_OPTS=-Xmx256M -Xms32M - -set _TOOL_CLASSPATH= -if "%_TOOL_CLASSPATH%"=="" ( - for %%f in ("%_SCALA_HOME%\lib\*") do call :add_cpath "%%f" - if "%OS%"=="Windows_NT" ( - for /d %%f in ("%_SCALA_HOME%\lib\*") do call :add_cpath "%%f" - ) -) - -set _PROPS=-Dscala.home="%_SCALA_HOME%" -Denv.classpath="%CLASSPATH%" -Denv.emacs="%EMACS%" - -rem echo "%_JAVACMD%" %_JAVA_OPTS% %_PROPS% -cp "%_TOOL_CLASSPATH%" scala.tools.nsc.MainGenericRunner %_ARGS% -"%_JAVACMD%" %_JAVA_OPTS% %_PROPS% -cp "%_TOOL_CLASSPATH%" scala.tools.nsc.MainGenericRunner %_ARGS% -goto end - -rem ########################################################################## -rem # subroutines - -:add_cpath - if "%_TOOL_CLASSPATH%"=="" ( - set _TOOL_CLASSPATH=%~1 - ) else ( - set _TOOL_CLASSPATH=%_TOOL_CLASSPATH%;%~1 - ) -goto :eof - -rem Variable "%~dps0" works on WinXP SP2 or newer -rem (see http://support.microsoft.com/?kbid=833431) -rem set _SCALA_HOME=%~dps0.. -:set_home - set _BIN_DIR= - for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi - set _SCALA_HOME=%_BIN_DIR%.. -goto :eof - -:set_args - set _ARGS= - :loop - rem Argument %1 may contain quotes so we use parentheses here - if (%1)==() goto :eof - set _ARGS=%_ARGS% %1 - shift - goto loop - -rem ########################################################################## -rem # errors - -:error1 -echo ERROR: environment variable SCALA_HOME is undefined. It should point to your installation directory. -goto end - -:end -if "%OS%"=="Windows_NT" @endlocal diff --git a/geoscript/src/main/assembly/doc/SCALA-LICENSE b/geoscript/src/main/assembly/doc/SCALA-LICENSE deleted file mode 100644 index fd4d83e..0000000 --- a/geoscript/src/main/assembly/doc/SCALA-LICENSE +++ /dev/null @@ -1,35 +0,0 @@ -SCALA LICENSE - -Copyright (c) 2002-2009 EPFL, Lausanne, unless otherwise specified. -All rights reserved. - -This software was developed by the Programming Methods Laboratory of the -Swiss Federal Institute of Technology (EPFL), Lausanne, Switzerland. - -Permission to use, copy, modify, and distribute this software in source -or binary form for any purpose with or without fee is hereby granted, -provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the EPFL nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - -THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff --git a/geoscript/src/main/assembly/doc/SCALA-README b/geoscript/src/main/assembly/doc/SCALA-README deleted file mode 100644 index f63f2e2..0000000 --- a/geoscript/src/main/assembly/doc/SCALA-README +++ /dev/null @@ -1,63 +0,0 @@ - -Scala Software Distributions ----------------------------- - -- scala-...tar.bz2 Unis distribution -- scala-...tar.gz Unix distribution -- scala-...zip Windows distribution - -The standard distributions require Java 1.4.x or above. If you don't -know which version of Java you have, run the command "java -version". - - -Scala Tools ------------ - -- fsc Scala offline compiler -- scalac Scala compiler -- scaladoc Scala API documentation generator -- scala Scala interactive interpreter -- scalap Scala classfile decoder - -Run the command "scalac -help" to display the list of available -compiler options. - - -Unix Installation ------------------ - -Untar the archive. All Scala tools are located in the "bin" directory. -Adding that directory to the PATH variable will make the Scala commands -directly accessible. - -You may test the distribution by running the following commands: - -$ ./bin/scalac share/doc/scala/examples/sort.scala -$ ./bin/scala examples.sort -[6,2,8,5,1] -[1,2,5,6,8] -$ ./bin/scala -scala> examples.sort.main(null) -[6,2,8,5,1] -[1,2,5,6,8] -scala>:quit -$ - - -Windows Installation --------------------- - -Unzip the archive. All Scala tools are located in the "bin" directory. -Adding that directory to the PATH variable will make the Scala commands -directly accessible. - -On Windows 95/98/Me, you must define the environment variable SCALA_HOME -to point to the home directory of the Scala distribution to run any of -these tools. This can be done by adding the following command to your -AUTOEXEC.BAT file and then rebooting your machine. - -set SCALA_HOME=\scala-.. - -On Windows NT/2000/XP, you do not need to define any variable in order -for the Scala commands to run correctly. - From ba830fd2606d600e74c5000a32e27387312c5536 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 17 Jun 2012 20:00:09 -0400 Subject: [PATCH 29/53] More API updates --- geoscript/src/main/scala/Converters.scala | 2 + .../src/main/scala/feature/package.scala | 29 +++++- .../src/main/scala/geometry/Geometry.scala | 11 +++ .../src/main/scala/geometry/Transform.scala | 39 +++++--- .../src/main/scala/geometry/package.scala | 7 ++ geoscript/src/main/scala/layer/package.scala | 1 + .../main/scala/projection/Projectable.scala | 23 +++++ .../main/scala/projection/Projection.scala | 17 +++- .../main/scala/projection/Referenced.scala | 25 ++++- geoscript/src/main/scala/render/package.scala | 91 +++++++++++++++++-- geoscript/src/main/scala/viewer/Viewer.scala | 60 +++--------- geoscript/src/test/scala/UsageTests.scala | 2 +- .../test/scala/tutorial/BasicGeometry.scala | 11 ++- .../scala/tutorial/GeometryAdvanced.scala | 33 +++++++ .../src/test/scala/tutorial/visualize.scala | 13 +++ 15 files changed, 278 insertions(+), 86 deletions(-) create mode 100644 geoscript/src/main/scala/projection/Projectable.scala create mode 100644 geoscript/src/test/scala/tutorial/GeometryAdvanced.scala create mode 100644 geoscript/src/test/scala/tutorial/visualize.scala diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 989b91e..a04ce95 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -13,6 +13,8 @@ package object geoscript { implicit def enrichEnvelope(e: Envelope) = new RichEnvelope(e) + implicit def enrichTransform(t: Transform) = new RichTransform(t) + implicit def enrichField(f: Field) = new RichField(f) implicit def enrichFeature(f: Feature) = new RichFeature(f) diff --git a/geoscript/src/main/scala/feature/package.scala b/geoscript/src/main/scala/feature/package.scala index 156fc56..fdd6fd1 100644 --- a/geoscript/src/main/scala/feature/package.scala +++ b/geoscript/src/main/scala/feature/package.scala @@ -9,13 +9,32 @@ package object feature { type GeoField = org.opengis.feature.`type`.GeometryDescriptor type Schema = org.opengis.feature.simple.SimpleFeatureType - def bind[T : Manifest](name: String): Field = - sys.error("Unimplemented") + def bind[T : Manifest](name: String): Field = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.setName(name) + builder.setBinding(manifest[T].erasure) + builder.buildDescriptor(name, builder.buildType()) + } def bind[T <: geometry.Geometry : Manifest] - (name: String, proj: projection.Projection): GeoField = sys.error("Unimplemented") + (name: String, proj: projection.Projection): GeoField = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.setName(name) + builder.setBinding(manifest[T].erasure) + builder.setCRS(proj) + builder.buildDescriptor(name, builder.buildGeometryType()) + } - def fromAttributes(attributes: (String, Any)*): Feature = sys.error("Unimplemented") + def fromAttributes(attributes: (String, Any)*): Feature = + sys.error("Unimplemented") + + def feature(schema: Schema, attributes: Seq[Any]): Feature = { + import org.geotools.feature.simple.SimpleFeatureBuilder + val builder = new SimpleFeatureBuilder(schema) + for ((value, idx) <- attributes.zipWithIndex) + builder.set(idx, value) + builder.buildFeature(null) + } } package feature { @@ -38,6 +57,8 @@ package feature { def get(name: String): Field = schema.getDescriptor(name) def get(index: Int): Field = schema.getDescriptor(index) def withName(name: String): Schema = sys.error("Unimplemented") + def feature(attributes: Seq[Any]): Feature = + org.geoscript.feature.feature(schema, attributes) } class RichFeature(feature: Feature) { diff --git a/geoscript/src/main/scala/geometry/Geometry.scala b/geoscript/src/main/scala/geometry/Geometry.scala index c77152f..7f98a16 100644 --- a/geoscript/src/main/scala/geometry/Geometry.scala +++ b/geoscript/src/main/scala/geometry/Geometry.scala @@ -39,6 +39,17 @@ class RichEnvelope(e: Envelope) { def maxX = e.getMaxX def minY = e.getMinY def minX = e.getMinX + + def && (that: Envelope): Envelope = e intersection that + + def || (that: Envelope): Envelope = + if (e.isNull) that + else if (that.isNull) e + else { + val res = new jts.Envelope(e) + res.expandToInclude(that) + res + } } class RichPoint(point: Point) extends RichGeometry(point) { diff --git a/geoscript/src/main/scala/geometry/Transform.scala b/geoscript/src/main/scala/geometry/Transform.scala index 9defd70..99d6ac8 100644 --- a/geoscript/src/main/scala/geometry/Transform.scala +++ b/geoscript/src/main/scala/geometry/Transform.scala @@ -1,23 +1,32 @@ package org.geoscript package geometry -import com.vividsolutions.jts.geom.util.AffineTransformation +import com.vividsolutions.jts.geom.util.{ + AffineTransformation, NoninvertibleTransformationException +} -class Transform(tx: AffineTransformation) { - def apply[G <: Geometry](g: G): G = { - val res = g.clone().asInstanceOf[G] - res.apply(tx) - res - } +class RichTransform(tx: Transform) { + def translated(dx: Double, dy: Double): Transform = + new AffineTransformation(tx).translate(dx, dy) - def translate(dx: Double, dy: Double): Transform = - new Transform(new AffineTransformation(tx).translate(dx, dy)) + def sheared(x: Double, y: Double): Transform = + new AffineTransformation(tx).shear(x, y) - def shear(shx: Double, shy: Double): Transform = - new Transform(new AffineTransformation(tx).shear(shx, shy)) + def scaled(x: Double, y: Double): Transform = + new AffineTransformation(tx).scale(x, y) - def scale(sx: Double, sy: Double): Transform = - new Transform(new AffineTransformation(tx).scale(sx, sy)) -} + def rotated(theta: Double, aboutX: Double = 0, aboutY: Double = 0): Transform = + new AffineTransformation(tx).rotate(theta, aboutX, aboutY) + + def reflected(x0: Double, y0: Double, x1: Double, y1: Double): Transform = + new AffineTransformation(tx).reflect(x0, y0, x1, y1) -object Transform extends Transform(new AffineTransformation) + def inverse: Option[Transform] = + try { + Some(tx.getInverse) + } catch { + case (_: NoninvertibleTransformationException) => None + } + + def apply[G <: Geometry](g: G): G = tx.transform(g).asInstanceOf[G] +} diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index 9f029f3..d8414d3 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -14,6 +14,7 @@ package object geometry { type Coordinate = jts.Coordinate type Envelope = jts.Envelope + type Transform = jts.util.AffineTransformation val EmptyEnvelope = new jts.Envelope @@ -94,4 +95,10 @@ package object geometry { else factory.createGeometryCollection(geoms.toArray) } + + def simplify(g: Geometry, tolerance: Double): Geometry = + com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier + .simplify(g, tolerance) + + def Transform = new jts.util.AffineTransformation } diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 33e9e47..cf92ce7 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -47,6 +47,7 @@ package layer { def ++= (features: Iterable[feature.Feature]) { layer.getFeatures().addAll(features.asJavaCollection) + withAll { fs => fs.foreach(println) } } def ++= (features: Iterator[feature.Feature]) { diff --git a/geoscript/src/main/scala/projection/Projectable.scala b/geoscript/src/main/scala/projection/Projectable.scala new file mode 100644 index 0000000..1cf230b --- /dev/null +++ b/geoscript/src/main/scala/projection/Projectable.scala @@ -0,0 +1,23 @@ +package org.geoscript +package projection + +import geometry._ +import org.geotools.geometry.jts.JTS + +trait Projectable[T] { + def project(from: Projection, to: Projection)(t: T): T +} + +object Projectable { + implicit def geometriesAreProjectable[T <: Geometry]: + Projectable[T] = new Projectable[T] { + def project(from: Projection, to: Projection)(t: T): T = + JTS.transform(t, transform(from, to)).asInstanceOf[T] + } + + implicit def envelopesAreProjectable: Projectable[Envelope] = + new Projectable[Envelope] { + def project(from: Projection, to: Projection)(e: Envelope) = + JTS.transform(e, transform(from, to)) + } +} diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index efdba8d..8ddab59 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -11,16 +11,25 @@ import org.geotools.referencing.CRS package object projection { type Projection = CoordinateReferenceSystem + type Transform = org.opengis.referencing.operation.MathTransform def fromWKT(s: String): Option[Projection] = Some(CRS.parseWKT(s)) def fromSrid(s: String): Option[Projection] = Some(CRS.decode(s)) - def transform[G <: geometry.Geometry] + def reproject[G : Projectable] (p: Projection, q: Projection) (g: G) - : G = sys.error("Unimplemented") + : G = implicitly[Projectable[G]].project(p, q)(g) + + def transform(p: Projection, q: Projection): Transform = + CRS.findMathTransform(p, q) def Projection(s: String): Projection = (fromSrid(s) orElse fromWKT(s)).orNull - lazy val LatLon = fromSrid("EPSG:4326") - lazy val WebMercator = fromSrid("EPSG:3857") + lazy val LatLon = fromSrid("EPSG:4326").get + lazy val WebMercator = fromSrid("EPSG:3857").get + + def reference[T : Projectable](t: T, proj: Projection): Referenced[T] = + Referenced(t, proj) + + def aspatial[T](t: T): Referenced[T] = Referenced(t) } diff --git a/geoscript/src/main/scala/projection/Referenced.scala b/geoscript/src/main/scala/projection/Referenced.scala index 612dfa4..6906de8 100644 --- a/geoscript/src/main/scala/projection/Referenced.scala +++ b/geoscript/src/main/scala/projection/Referenced.scala @@ -1,14 +1,19 @@ package org.geoscript package projection +import geometry.Envelope +import org.geotools.geometry.jts.ReferencedEnvelope + trait Referenced[T] { def map[U](f: T => U): Referenced[U] def flatMap[U](f: T => Referenced[U]): Referenced[U] + def native: Option[Projection] def force(p: Projection): T + def forceNative: T = force(native.get) } object Referenced { - def apply[T <: geometry.Geometry](value: T, projection: Projection) + def apply[T : Projectable](value: T, projection: Projection) : Referenced[T] = new Physical(value, projection) def apply[T](value: T): Referenced[T] = new Ideal(value) @@ -17,9 +22,10 @@ object Referenced { def map[U](f: T => U): Referenced[U] = new Ideal(f(value)) def flatMap[U](f: T => Referenced[U]): Referenced[U] = f(value) def force(p: Projection): T = value + def native = None } - private class Physical[T <: geometry.Geometry](value: T, proj: Projection) + private class Physical[T : Projectable](value: T, proj: Projection) extends Referenced[T] { def map[U](f: T => U): Referenced[U] = new Mapped(this, f) @@ -28,7 +34,9 @@ object Referenced { new FlatMapped(this, f) def force(p: Projection): T = - (org.geoscript.projection.transform(proj, p)(value)) + implicitly[Projectable[T]].project(proj, p)(value) + + def native = Some(proj) } private class Mapped[T, U](base: Referenced[T], f: T => U) extends Referenced[U] { @@ -39,6 +47,8 @@ object Referenced { new FlatMapped(base, f andThen g) def force(p: Projection): U = f(base.force(p)) + + def native = base.native } private class FlatMapped[T, U](base: Referenced[T], f: T => Referenced[U]) @@ -50,5 +60,14 @@ object Referenced { def force(p: Projection): U = f(base.force(p)).force(p) + + def native = base.native } + + import org.geotools.geometry.jts.ReferencedEnvelope + implicit def referenceEnvelope(renv: ReferencedEnvelope): Referenced[Envelope] = + Referenced(renv, renv.getCoordinateReferenceSystem) + + def envelope(renv: Referenced[Envelope]): ReferencedEnvelope = + new ReferencedEnvelope(renv.forceNative, renv.native.orNull) } diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index 944724f..34c439c 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -1,5 +1,7 @@ package org.geoscript +import style.combinators._, feature._, geometry._, projection._ + import collection.JavaConverters._ import org.{ geotools => gt } import gt.geometry.jts.ReferencedEnvelope @@ -10,14 +12,63 @@ import java.awt.RenderingHints, package render { trait Stylable[T] { def applyStyle(t: T, s: style.Style): Layer + def defaultStyle(t: T): Layer } object Stylable { - def stylable[T](f: (T, style.Style) => Layer): Stylable[T] = - new Stylable[T] { def applyStyle(t: T, s: style.Style): Layer = f(t, s) } + private val defaultSchema = + Schema("empty", Seq( + bind[Geometry]("the_geom", projection.LatLon))) + + def by[T, U : Stylable](f: T => U): Stylable[T] = + new Stylable[T] { + def applyStyle(t: T, s: style.Style): Layer = + implicitly[Stylable[U]].applyStyle(f(t), s) + + def defaultStyle(t: T): Layer = + implicitly[Stylable[U]].defaultStyle(f(t)) + } implicit val vectorDataIsStylable: Stylable[layer.Layer] = - stylable { (l, s) => new gt.map.FeatureLayer(l, s.underlying, l.name) } + new Stylable[layer.Layer] { + def applyStyle(l: layer.Layer, s: style.Style): Layer = + new gt.map.FeatureLayer(l, s.underlying, l.name) + + def defaultStyle(l: layer.Layer): Layer = { + val Point = classOf[Point] + val MultiPoint = classOf[MultiPoint] + val LineString = classOf[LineString] + val MultiLineString = classOf[MultiLineString] + val sty = + l.schema.geometry.binding match { + case Point | MultiPoint => Symbol("square") + case LineString | MultiLineString => Stroke("black") + case _ => Fill("grey") and Stroke("black") + } + new gt.map.FeatureLayer(l, sty.underlying, l.name) + } + } + + implicit val seqOfFeaturesIsStylable: Stylable[Seq[Feature]] = + Stylable.by { fs: Seq[Feature] => + val schema = fs.headOption + .map (_.getFeatureType) + .getOrElse(defaultSchema) + + val store = new org.geotools.data.memory.MemoryDataStore(fs.toArray) + store.layerNamed(store.names.head) + } + + implicit val singleFeatureIsStylable: Stylable[Feature] = + Stylable.by { (f: Feature) => Seq(f) } + + implicit val geometryIsStylable: Stylable[Geometry] = + Stylable.by { (g: Geometry) => defaultSchema.feature(Seq(g)) } + + implicit val seqOfGeometriesIsStylable: Stylable[Seq[Geometry]] = + Stylable.by { (_: Seq[Geometry]).map { g => + defaultSchema.feature(Seq(g)) + } } } trait Canvas[T] { self => @@ -71,6 +122,8 @@ package render { object Content { def empty = new Content + def apply[T : Stylable](t: T): Content = + empty.withData(t) def apply[T : Stylable](t: T, s: style.Style): Content = empty.withData(t, s) } @@ -84,6 +137,9 @@ package render { } def withData[T : Stylable](data: T, s: style.Style): Content = withLayer(applyStyle(data, s)) + + def withData[T : Stylable](data: T): Content = + withLayer(implicitly[Stylable[T]].defaultStyle(data)) } } @@ -100,13 +156,9 @@ package object render { def draw[Out]( content: Content, - // layers: Seq[Layer], bounds: ReferencedEnvelope, canvas: Canvas[Out] = new ImageCanvas ): Out = { - // val content = new gt.map.MapContent - // layers.foreach { content.addLayer } - // val hints = renderHints(KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON) canvas.render { (graphics, screenArea) => @@ -116,6 +168,31 @@ package object render { renderer.paint(graphics, tupleAsRectangle(screenArea), bounds) } } + + def drawFull[Out]( + content: Content, + canvas: Canvas[Out] = new ImageCanvas + ): Out = { + def mode[A](xs: Seq[A]): Option[A] = + if (xs.isEmpty) None + else Some(xs.groupBy(identity).maxBy(_._2.size)._1) + + val boundsList = content.layers.asScala.map(_.getBounds) + val projectionList = boundsList.map(_.getCoordinateReferenceSystem) + + val projection = mode(projectionList).getOrElse(LatLon) + + val expand = (a: Referenced[Envelope], b: Referenced[Envelope]) => + Referenced.envelope(for (aEnv <- a; bEnv <- b) yield aEnv || bEnv) + + val bounds = (boundsList reduceLeftOption (expand(_, _))) + + val finalBounds = bounds + .map(Referenced.envelope(_)) + .getOrElse(new ReferencedEnvelope(EmptyEnvelope, LatLon)) + + draw(content, finalBounds, canvas) + } def file(f: String) = new java.io.File(f) diff --git a/geoscript/src/main/scala/viewer/Viewer.scala b/geoscript/src/main/scala/viewer/Viewer.scala index dd8ae7a..bf3074b 100644 --- a/geoscript/src/main/scala/viewer/Viewer.scala +++ b/geoscript/src/main/scala/viewer/Viewer.scala @@ -1,59 +1,21 @@ package org.geoscript package object viewer { - def showWindow() { - val window = new javax.swing.JFrame - window.setDefaultCloseOperation( - javax.swing.WindowConstants.DISPOSE_ON_CLOSE) - window.setSize(512, 512) - window.setVisible(true) - window.add(geometryComponent) - } - - private var visibleGeometries = Seq.empty[geometry.Geometry] +} - def worldToScreen( - rect: java.awt.Rectangle, - env: geometry.Envelope - ): java.awt.geom.AffineTransform = { - val xscale = rect.getWidth / env.getWidth - val yscale = rect.getHeight / env.getHeight - val scale = math.min(xscale, yscale) - val deltax = rect.getX - env.getMinX - val deltay = rect.getY - env.getMinY - val tx = new java.awt.geom.AffineTransform() - tx.scale(scale, scale) - tx.translate(deltax, deltay) - tx +package viewer { + trait Viewable[V] { + def bounds(v: V): geometry.Envelope + def draw(v: V, canvas: java.awt.Graphics2D): Unit } - - val geometryComponent = - new javax.swing.JComponent { - setPreferredSize(new java.awt.Dimension(512, 512)) - - override def paint(graphics: java.awt.Graphics) { - import org.geotools.geometry.jts.LiteShape - import org.geoscript.geometry._ - val envelope = - visibleGeometries.foldLeft(EmptyEnvelope) { - (e: Envelope, g: Geometry) => union(e, g.getEnvelopeInternal) - } - val transform = worldToScreen(getBounds(), envelope) - val canvas = graphics.asInstanceOf[java.awt.Graphics2D] - visibleGeometries.foreach { g => - val shp = new LiteShape(g, transform, true) - if (g.getArea > 0) - canvas.fill(shp) - canvas.draw(shp) - } - } - } + object Viewable { + implicit object geometryIsViewable extends Viewable[geometry.Geometry] { + def bounds(g: geometry.Geometry): geometry.Envelope = + g.getEnvelopeInternal() - def draw(g: geometry.Geometry) { - synchronized { - visibleGeometries :+= g + def draw(g: geometry.Geometry, canvas: java.awt.Graphics2D) { + } } - geometryComponent.repaint() } } diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 77d7af6..673a099 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -9,7 +9,7 @@ class UsageTests extends Specification { "geometries" should { "work like on the geoscript homepage" in { var p = point(-111, 45.7) - var p2 = (projection.transform(Projection("epsg:4326"), Projection("epsg:26912")))(p) + var p2 = (projection.reproject(Projection("epsg:4326"), Projection("epsg:26912")))(p) var poly = p.buffer(100) p2.x must beCloseTo(499999.0, 1) diff --git a/geoscript/src/test/scala/tutorial/BasicGeometry.scala b/geoscript/src/test/scala/tutorial/BasicGeometry.scala index 39f6951..c70f9c4 100644 --- a/geoscript/src/test/scala/tutorial/BasicGeometry.scala +++ b/geoscript/src/test/scala/tutorial/BasicGeometry.scala @@ -51,7 +51,12 @@ object BasicGeometry extends App { // operations: derived geometries line.buffer(1) - // (de)serialization - // geometry.WKT.write(point) - // geometry.GeoJSON.write(point) + // serialization + import org.geoscript.serialize.{ Sink, Source } + geometry.WKT.write(point, Sink.string) + geometry.GeoJSON.write(point, Sink.string) + + // deserialization + geometry.WKT.read(Source.string("POINT (30 10)")) + geometry.GeoJSON.read(Source.string("POINT (30 10)")) } diff --git a/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala b/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala new file mode 100644 index 0000000..7099c66 --- /dev/null +++ b/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala @@ -0,0 +1,33 @@ +package tutorial + +import org.geoscript._, geometry._, render._ + +object GeometryBuffer extends App { + val poly = point(0, 0).buffer(1) + drawFull(Content(poly), new Window) +} + +object GeometrySimplify extends App { + val poly = point(0, 0).buffer(1) + drawFull(Content(simplify(poly, 0.05)), new Window) + drawFull(Content(simplify(poly, 0.1)), new Window) +} + +object GeometryTransform extends App { + val circle = point(0, 0).buffer(1) + drawFull( + Content(Seq(circle, Transform.translated(dx=0.75, dy=0)(circle))), + new Window) + + val box = polygon(Seq((0, 0), (1, 0), (1, 1), (0, 1), (0, 0))) + drawFull( + Content(Seq( + box, Transform.sheared(x=1, y=0).scaled(x=2, y=2)(box))), + new Window) + + val bar = polygon(Seq((-5,-2),(5,-2),(5,2),(-5,2), (-5,-2))) + val cross = bar union Transform.rotated(theta=math.toRadians(90))(bar) + drawFull( + Content(Seq(cross, Transform.rotated(theta=math.toRadians(45))(cross))), + new Window) +} diff --git a/geoscript/src/test/scala/tutorial/visualize.scala b/geoscript/src/test/scala/tutorial/visualize.scala new file mode 100644 index 0000000..ad5493f --- /dev/null +++ b/geoscript/src/test/scala/tutorial/visualize.scala @@ -0,0 +1,13 @@ +package tutorial + +import org.geoscript._, render._ +import org.geotools.geometry.jts.ReferencedEnvelope + +object Visualize extends App { + val poly = geometry.polygon( + Seq((35,10), (10,20), (15,40), (45,45), (35,10)), + Seq(Seq((20,30), (35,35), (30,20), (20,30)))) + val bounds = new ReferencedEnvelope(poly.getEnvelopeInternal, projection.LatLon) + val window = new Window + draw(Content(poly), bounds, window) +} From d7323e29c2fc273c9f3ffdcd138e11524e363c8a Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 17 Jun 2012 20:23:20 -0400 Subject: [PATCH 30/53] Voronoi and delaunay wrappers --- .../src/main/scala/geometry/package.scala | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index d8414d3..055b25e 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -100,5 +100,59 @@ package object geometry { com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier .simplify(g, tolerance) + def delaunay(g: Geometry): Delaunay = { + val triangulator = new com.vividsolutions.jts.triangulate.DelaunayTriangulationBuilder + triangulator.setSites(g) + new Delaunay { + lazy val edges = triangulator.getEdges(factory) + lazy val triangles = triangulator.getTriangles(factory) + } + } + + def delaunayTol(g: Geometry, tolerance: Double): Delaunay = { + val triangulator = new com.vividsolutions.jts.triangulate.DelaunayTriangulationBuilder + triangulator.setSites(g) + triangulator.setTolerance(tolerance) + new Delaunay { + lazy val edges = triangulator.getEdges(factory) + lazy val triangles = triangulator.getTriangles(factory) + } + } + + def voronoi(g: Geometry): Geometry = { + val voronoi = new com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder + voronoi.setSites(g) + voronoi.getDiagram(factory) + } + + def voronoiEnv(g: Geometry, e: Envelope): Geometry = { + val voronoi = new com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder + voronoi.setSites(g) + voronoi.setClipEnvelope(e) + voronoi.getDiagram(factory) + } + + def voronoiEnvTol(g: Geometry, e: Envelope, tolerance: Double): Geometry = { + val voronoi = new com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder + voronoi.setSites(g) + voronoi.setClipEnvelope(e) + voronoi.setTolerance(tolerance) + voronoi.getDiagram(factory) + } + + def voronoiTol(g: Geometry, tolerance: Double): Geometry = { + val voronoi = new com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder + voronoi.setSites(g) + voronoi.setTolerance(tolerance) + voronoi.getDiagram(factory) + } + def Transform = new jts.util.AffineTransformation } + +package geometry { + sealed trait Delaunay { + def edges: Geometry + def triangles: Geometry + } +} From 91c21829dc7af04b2eb7082d7363c65d2d3ea9b7 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 17 Jun 2012 20:48:24 -0400 Subject: [PATCH 31/53] Projection enhancements --- geoscript/src/main/scala/Converters.scala | 4 ++++ .../main/scala/projection/Projection.scala | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index a04ce95..5741fb6 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -5,6 +5,7 @@ package object geoscript { import layer._ import workspace._ import render._ + import projection.{ Projection, RichProjection } implicit def enrichGeometry(g: Geometry): RichGeometry = new RichGeometry(g) @@ -33,4 +34,7 @@ package object geoscript { implicit def enrichContent(content: Content): RichContent = new RichContent(content) + + implicit def enrichProjection(p: Projection): RichProjection = + new RichProjection(p) } diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index 8ddab59..9a80f02 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -1,9 +1,12 @@ package org.geoscript +import scala.util.control.Exception.catching + import com.vividsolutions.jts.{geom => jts} import org.opengis.referencing.crs.CoordinateReferenceSystem import org.opengis.referencing.operation.MathTransform +import org.opengis.referencing.{ FactoryException, NoSuchAuthorityCodeException } import org.geotools.factory.Hints import org.geotools.geometry.jts.JTS @@ -13,8 +16,16 @@ package object projection { type Projection = CoordinateReferenceSystem type Transform = org.opengis.referencing.operation.MathTransform - def fromWKT(s: String): Option[Projection] = Some(CRS.parseWKT(s)) - def fromSrid(s: String): Option[Projection] = Some(CRS.decode(s)) + private val catchLookup = catching(classOf[FactoryException]) + + def fromWKT(s: String): Option[Projection] = + catching(classOf[FactoryException]).opt { CRS.parseWKT(s) } + + def fromSrid(s: String): Option[Projection] = + catching(classOf[FactoryException], classOf[NoSuchAuthorityCodeException]).opt { + CRS.decode(s) + } + def reproject[G : Projectable] (p: Projection, q: Projection) (g: G) @@ -33,3 +44,9 @@ package object projection { def aspatial[T](t: T): Referenced[T] = Referenced(t) } + +package projection { + class RichProjection(p: Projection) { + def id = CRS.lookupIdentifier(p, true) + } +} From 9cda9178c0a06bd57ecc4c00042448d23f4d3d48 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Thu, 21 Jun 2012 11:21:58 -0400 Subject: [PATCH 32/53] Interim commit --- geoscript/src/main/scala/feature/package.scala | 2 ++ geoscript/src/main/scala/layer/package.scala | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/geoscript/src/main/scala/feature/package.scala b/geoscript/src/main/scala/feature/package.scala index fdd6fd1..442035c 100644 --- a/geoscript/src/main/scala/feature/package.scala +++ b/geoscript/src/main/scala/feature/package.scala @@ -35,6 +35,8 @@ package object feature { builder.set(idx, value) builder.buildFeature(null) } + + def widen(a: Seq[Field], b: Seq[Field]): Seq[Field] = sys.error("Unimplemented") } package feature { diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index cf92ce7..6b4e7ea 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -2,12 +2,24 @@ package org.geoscript import scala.collection.JavaConverters._ +import feature.{ Feature, Field, Schema } import filter.Filter import workspace.Workspace package object layer { type Layer = org.geotools.data.simple.SimpleFeatureStore type Query = org.geotools.data.Query + + def Layer(s: feature.Schema): Layer = sys.error("Undefined") + + def Layer(name: String, fs: Iterable[feature.Feature]): Layer = { + def undefined = sys.error("Undefined") + implicit def f(f: Feature): { def schema: Schema } = undefined + implicit def widen(f0: Seq[Field], f1: Seq[Field]): Seq[Field] = undefined + val attributes = fs.map(_.schema.fields).foldLeft(Seq.empty[feature.Field])(widen) + val schema: Schema = Schema(name, attributes) + sys.error("undefined") + } } package layer { From 7084c997a735a77cde332a9e2fc6092a32183714 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Fri, 6 Jul 2012 23:22:25 -0400 Subject: [PATCH 33/53] Rename package source for feature package --- .../src/main/scala/feature/Feature.scala | 302 ------------------ .../feature/{package.scala => feature.scala} | 0 2 files changed, 302 deletions(-) delete mode 100644 geoscript/src/main/scala/feature/Feature.scala rename geoscript/src/main/scala/feature/{package.scala => feature.scala} (100%) diff --git a/geoscript/src/main/scala/feature/Feature.scala b/geoscript/src/main/scala/feature/Feature.scala deleted file mode 100644 index 512340e..0000000 --- a/geoscript/src/main/scala/feature/Feature.scala +++ /dev/null @@ -1,302 +0,0 @@ -package org.geoscript.feature - -import com.vividsolutions.jts.{geom => jts} -import org.geoscript.geometry._ -import org.geoscript.projection._ -import org.{geotools => gt} -import org.opengis.feature.simple.{SimpleFeature, SimpleFeatureType} -import org.opengis.feature.`type`.{AttributeDescriptor, GeometryDescriptor} - -// /** -// * A Schema enumerates the types and names of the properties of records in a -// * particular dataset. For example, a Schema for a road dataset might have -// * fields like "name", "num_lanes", "the_geom". -// */ -// trait Schema { -// /** -// * The name of the dataset itself. This is not a property of the data records. -// */ -// def name: String -// -// /** -// * The geometry field for this layer, regardless of its name. -// */ -// def geometry: GeoField -// -// /** -// * All fields in an iterable sequence. -// */ -// def fields: Seq[Field] -// -// /** -// * The names of all fields in an iterable sequence. -// */ -// def fieldNames: Seq[String] = fields map { _.name } -// -// /** -// * Retrieve a field by name. -// */ -// def get(fieldName: String): Field -// -// /** -// * Create an instance of a feature according to this Schema, erroring if: -// *
    -// *
  • any values are omitted
  • -// *
  • any values are the wrong type
  • -// *
  • any extra values are present
  • -// *
-// */ -// def create(data: (String, AnyRef)*): Feature = { -// if ( -// data.length != fields.length || -// data.exists { case (key, value) => !get(key).gtBinding.isInstance(value) } -// ) { -// throw new RuntimeException( -// "Can't create feature; properties are: %s, but fields require %s.".format( -// data.mkString, fields.mkString -// ) -// ) -// } -// Feature(data: _*) -// } -// -// def underlying: SimpleFeatureType -// -// override def toString: String = { -// "".format( -// name, -// fields.mkString("[", ", ", "]") -// ) -// } -// } - -// /** -// * A companion object for Schema that provides various ways of creating Schema -// * instances. -// */ -// object Schema { -// def apply(wrapped: SimpleFeatureType) = { -// new Schema { -// def name = wrapped.getTypeName() -// def geometry = Field(wrapped.getGeometryDescriptor()) -// -// def fields: Seq[Field] = { -// var buffer = new collection.mutable.ArrayBuffer[Field] -// val descriptors = wrapped.getAttributeDescriptors().iterator() -// while (descriptors hasNext) { buffer += Field(descriptors.next) } -// buffer.toSeq -// } -// -// def get(fieldName: String) = Field(wrapped.getDescriptor(fieldName)) -// -// def underlying = wrapped -// } -// } -// -// def apply(n: String, f: Field*): Schema = apply(n, f.toSeq) -// -// def apply(n: String, f: Iterable[Field]): Schema = { -// new Schema { -// def name = n -// def geometry = -// f.find(_.isInstanceOf[GeoField]) -// .getOrElse(null) -// .asInstanceOf[GeoField] -// -// def fields = f.toSeq -// def get(fieldName: String) = f.find(_.name == fieldName).get -// def underlying = sys.error("Unimplemented") -// } -// } -// } - -// /** -// * A Field represents a particular named, typed property in a Schema. -// */ -// trait Field { -// def name: String -// def gtBinding: Class[_] -// override def toString = "%s: %s".format(name, gtBinding.getSimpleName) -// } -// -// /** -// * A Field that represents a Geometry. GeoFields add projection information to -// * normal fields. -// */ -// trait GeoField extends Field { -// override def gtBinding: Class[_] -// /** -// * The Projection used for this field's geometry. -// */ -// def projection: Projection -// -// def copy(projection: Projection): GeoField = { -// val n = name -// val gb = gtBinding -// val p = projection -// -// new GeoField { -// val name = n -// override val gtBinding = gb -// val projection = p -// } -// } -// -// override def toString = "%s: %s [%s]".format(name, gtBinding.getSimpleName, projection) -// } -// -// /** -// * A companion object providing various methods of creating Field instances. -// */ -// object Field { -// /** -// * Create a GeoField by wrapping an OpenGIS GeometryDescriptor -// */ -// def apply(wrapped: GeometryDescriptor): GeoField = -// new GeoField { -// def name = wrapped.getLocalName -// override def gtBinding = wrapped.getType.getBinding -// def projection = Projection(wrapped.getCoordinateReferenceSystem()) -// } -// -// /** -// * Create a Field by wrapping an OpenGIS AttributeDescriptor -// */ -// def apply(wrapped: AttributeDescriptor): Field = { -// wrapped match { -// case geom: GeometryDescriptor => apply(geom) -// case wrapped => -// new Field { -// def name = wrapped.getLocalName -// def gtBinding = wrapped.getType.getBinding -// } -// } -// } -// -// def apply[G : BoundGeometry](n: String, b: Class[G], p: Projection): GeoField = -// new GeoField { -// def name = n -// def gtBinding = implicitly[BoundGeometry[G]].binding -// def projection = p -// } -// -// def apply[S : BoundScalar](n: String, b: Class[S]): Field = -// new Field { -// def name = n -// def gtBinding = implicitly[BoundScalar[S]].binding -// } -// } - -/** - * A Feature represents a record in a geospatial data set. It should generally - * identify a single "thing" such as a landmark or observation. - */ -// trait Feature { -// /** -// * An identifier for this feature in the dataset. -// */ -// def id: String -// -// /** -// * Retrieve a property of the feature, with an expected type. Typical usage is: -// *
-//    * val name = feature.get[String]("name")
-//    * 
-// */ -// def get[A](key: String): A -// -// /** -// * Get the geometry for this feature. This allows you to access the geometry -// * without worrying about its property name. -// */ -// def geometry: Geometry -// -// /** -// * Get all properties for this feature as a Map. -// */ -// def properties: Map[String, Any] -// -// def update(data: (String, Any)*): Feature = update(data.toSeq) -// -// def update(data: Iterable[(String, Any)]): Feature = { -// val props = properties -// assert(data.forall { x => props contains x._1 }) -// Feature(props ++ data) -// } -// -// /** -// * Write the values in this Feature to a particular OGC Feature object. -// */ -// def writeTo(feature: org.opengis.feature.simple.SimpleFeature) { -// for ((k, v) <- properties) feature.setAttribute(k, v) -// } -// -// def underlying: org.opengis.feature.simple.SimpleFeature -// -// override def toString: String = -// properties map { -// case (key, value: jts.Geometry) => -// "%s: <%s>".format(key, value.getGeometryType()) -// case (key, value) => -// "%s: %s".format(key, value) -// } mkString("") -// } - -// /** -// * A companion object for Feature providing several methods for creating -// * Feature instances. -// */ -// object Feature { -// /** -// * Create a GeoScript feature by wrapping a GeoAPI feature instance. -// */ -// def apply(wrapped: SimpleFeature): Feature = { -// new Feature { -// def id: String = wrapped.getID -// -// def get[A](key: String): A = -// wrapped.getAttribute(key).asInstanceOf[A] -// -// def geometry: Geometry = -// wrapped.getDefaultGeometry().asInstanceOf[Geometry] -// -// def properties: Map[String, Any] = { -// val pairs = -// for { -// i <- 0 until wrapped.getAttributeCount -// key = wrapped.getType().getDescriptor(i).getLocalName -// value = get[Any](key) -// } yield (key -> value) -// pairs.toMap -// } -// -// def underlying = wrapped -// } -// } -// -// def apply(props: (String, Any)*): Feature = apply(props) -// -// /** -// * Create a feature from name/value pairs. Example usage looks like: -// *
-//    * val feature = Feature("geom" -> Point(12, 37), "type" -> "radio tower")
-//    * 
-// */ -// def apply(props: Iterable[(String, Any)]): Feature = { -// new Feature { -// def id: String = null -// -// def geometry = -// props.collectFirst({ -// case (name, geom: Geometry) => geom -// }).get -// -// def get[A](key: String): A = -// props.find(_._1 == key).map(_._2.asInstanceOf[A]).get -// -// def underlying: org.opengis.feature.simple.SimpleFeature = sys.error("Unimplemented") -// -// def properties: Map[String, Any] = Map(props.toSeq: _*) -// } -// } -// } diff --git a/geoscript/src/main/scala/feature/package.scala b/geoscript/src/main/scala/feature/feature.scala similarity index 100% rename from geoscript/src/main/scala/feature/package.scala rename to geoscript/src/main/scala/feature/feature.scala From b1f5506b452254e524c852774bc6e414387a5d8b Mon Sep 17 00:00:00 2001 From: David Winslow Date: Fri, 6 Jul 2012 23:24:44 -0400 Subject: [PATCH 34/53] Start implementing some functions --- .../src/main/scala/feature/feature.scala | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index 442035c..80b460e 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -25,8 +25,20 @@ package object feature { builder.buildDescriptor(name, builder.buildGeometryType()) } - def fromAttributes(attributes: (String, Any)*): Feature = - sys.error("Unimplemented") + private def field(name: String, binding: Class[_]): Field = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.setName(name) + builder.setBinding(binding) + builder.buildDescriptor(name, builder.buildType) + } + + def fromAttributes(attributes: (String, Any)*): Feature = { + val fields = attributes.map { case (n, v) => field(n, v.getClass) } + val schema = Schema("internal", fields) + val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) + for ((key, value) <- attributes) builder.set(key, value) + builder.buildFeature(null) + } def feature(schema: Schema, attributes: Seq[Any]): Feature = { import org.geotools.feature.simple.SimpleFeatureBuilder @@ -58,16 +70,17 @@ package feature { def geometry = schema.getGeometryDescriptor def get(name: String): Field = schema.getDescriptor(name) def get(index: Int): Field = schema.getDescriptor(index) - def withName(name: String): Schema = sys.error("Unimplemented") + def withName(name: String): Schema = Schema(name, fields) def feature(attributes: Seq[Any]): Feature = org.geoscript.feature.feature(schema, attributes) } class RichFeature(feature: Feature) { - def id: String = sys.error("unimplemented") - def get[A](index: Int): A = sys.error("Unimplemented") - def get[A](key: String): A = sys.error("Unimplemented") - def geometry: org.geoscript.geometry.Geometry = sys.error("Unimplemented") + def id: String = feature.getID + def get[A](index: Int): A = feature.getAttribute(index).asInstanceOf[A] + def get[A](key: String): A = feature.getAttribute(key).asInstanceOf[A] + def geometry: org.geoscript.geometry.Geometry = + feature.getDefaultGeometry.asInstanceOf[org.geoscript.geometry.Geometry] } class RichField(field: Field) { From 0c391d36a14e05bd534cdb2f510d89a799108649 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 8 Jul 2012 14:07:08 -0400 Subject: [PATCH 35/53] Abandon 'widen' idea for feature schemas --- geoscript/src/main/scala/feature/feature.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index 80b460e..28793c1 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -47,8 +47,6 @@ package object feature { builder.set(idx, value) builder.buildFeature(null) } - - def widen(a: Seq[Field], b: Seq[Field]): Seq[Field] = sys.error("Unimplemented") } package feature { From eada0319441288f349fc9e10d995621db5c18530 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 8 Jul 2012 14:10:17 -0400 Subject: [PATCH 36/53] Implement RichGeoField#withProjection --- geoscript/src/main/scala/feature/feature.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index 28793c1..775e779 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -87,6 +87,11 @@ package feature { } class RichGeoField(field: GeoField) { - def withProjection(proj: projection.Projection): GeoField = sys.error("Unimplemented") + def withProjection(proj: projection.Projection): GeoField = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.init(field) + builder.setCRS(proj) + builder.buildDescriptor(field.getName, builder.buildGeometryType()) + } } } From 09ec724b8bc48234169345238577311ecf2923fe Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 8 Jul 2012 22:01:15 -0400 Subject: [PATCH 37/53] Fix adding features to layers --- geoscript/src/main/scala/layer/package.scala | 61 ++++++++++++++++--- .../src/test/scala/tutorial/layers.scala | 12 ++++ 2 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 geoscript/src/test/scala/tutorial/layers.scala diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 6b4e7ea..3fe03ce 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -10,13 +10,50 @@ package object layer { type Layer = org.geotools.data.simple.SimpleFeatureStore type Query = org.geotools.data.Query - def Layer(s: feature.Schema): Layer = sys.error("Undefined") + def Layer(name: String, fields: Seq[Field]): Layer = { + Layer(Schema(name, fields)) + } + + def Layer(s: feature.Schema): Layer = { + val workspace = new org.geotools.data.memory.MemoryDataStore + workspace.createSchema(s) + workspace.getFeatureSource(s.name).asInstanceOf[Layer] + } + + def Layer(name: String, fs: Traversable[feature.Feature]): Layer = { + implicit def featureHasSchema(f: Feature): { def schema: Schema } = + new { + def schema = f.getFeatureType + } - def Layer(name: String, fs: Iterable[feature.Feature]): Layer = { - def undefined = sys.error("Undefined") - implicit def f(f: Feature): { def schema: Schema } = undefined - implicit def widen(f0: Seq[Field], f1: Seq[Field]): Seq[Field] = undefined - val attributes = fs.map(_.schema.fields).foldLeft(Seq.empty[feature.Field])(widen) + implicit def widen(f0: Seq[Field], f1: Seq[Field]): Seq[Field] = { + val names0 = (f0 map (_.name)) + val names1 = (f1 map (_.name)) + val names = names0 ++ (names1.filterNot(names0.contains)) + val byName0 = (f0 map (x => (x.name, x))).toMap + val byName1 = (f1 map (x => (x.name, x))).toMap + + names.map { n => + ((byName0 get n), (byName1 get n)) match { + case (None, None) => + sys.error("Unexpected state... name " + n + "not found in either schema in widen method") + case (Some(a), None) => + a + case (None, Some(b)) => + b + case (Some(a), Some(b)) => + if (a != b) + sys.error("Incompatible field types: " + a + ", " + b) + else + a + } + } + } + val attributes = + (fs foldLeft Seq.empty[Field]) { + (fs: Seq[Field], f: Feature) => + widen(fs, f.schema.fields) + } val schema: Schema = Schema(name, attributes) sys.error("undefined") } @@ -58,8 +95,16 @@ package layer { def += (fs: feature.Feature*) = this ++= fs def ++= (features: Iterable[feature.Feature]) { - layer.getFeatures().addAll(features.asJavaCollection) - withAll { fs => fs.foreach(println) } + val tx = new org.geotools.data.DefaultTransaction + layer.setTransaction(tx) + try { + val featureColl = org.geotools.feature.FeatureCollections.newCollection() + featureColl.addAll(features.asJavaCollection) + layer.addFeatures(featureColl) + tx.commit() + } catch { + case ex => tx.rollback(); throw ex + } } def ++= (features: Iterator[feature.Feature]) { diff --git a/geoscript/src/test/scala/tutorial/layers.scala b/geoscript/src/test/scala/tutorial/layers.scala new file mode 100644 index 0000000..08b9ab4 --- /dev/null +++ b/geoscript/src/test/scala/tutorial/layers.scala @@ -0,0 +1,12 @@ +package tutorial + +object Layers extends App { + import org.geoscript._, layer._ + val lyr = Layer("foo", Seq(feature.bind[String]("text"))) + lyr += feature.fromAttributes(("text", "hello")) + lyr.withAll { features => + for (feature <- features) { + println(feature.get[String]("text")) + } + } +} From ed03aef7dd81706344147d05dbdad5df1d5e8d6a Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 31 Jul 2012 22:00:29 -0400 Subject: [PATCH 38/53] Add some implicit magic in the serialize package Now client code looks like: GeoJSON.read(text) Instead of: GeoJSON.read(Source.string(text)) --- .../src/main/scala/geometry/io/package.scala | 54 ++--- .../src/main/scala/serialize/package.scala | 195 +++++++++++------- .../geometry/SerializationSpec.scala | 26 ++- .../test/scala/tutorial/BasicGeometry.scala | 9 +- 4 files changed, 161 insertions(+), 123 deletions(-) diff --git a/geoscript/src/main/scala/geometry/io/package.scala b/geoscript/src/main/scala/geometry/io/package.scala index bd9c06a..5b17017 100644 --- a/geoscript/src/main/scala/geometry/io/package.scala +++ b/geoscript/src/main/scala/geometry/io/package.scala @@ -6,58 +6,44 @@ object WKT extends Format[Geometry] { private val reader = new com.vividsolutions.jts.io.WKTReader() private val writer = new com.vividsolutions.jts.io.WKTWriter() - def read(source: Source): Geometry = - source { in => - val chars = new java.io.InputStreamReader(in) - val res = reader.read(chars) - chars.close() - - res - } - - def write[T](geom: Geometry, sink: Sink[T]): T = - sink { out => - val chars = new java.io.OutputStreamWriter(out) - writer.write(geom, chars) - chars.close() - } + def readFrom(in: java.io.Reader): Geometry = reader.read(in) + + def writeTo(out: java.io.Writer, geom: Geometry) { writer.write(geom, out) } } -object WKB extends Format[Geometry] { +object WKB extends Codec[Geometry] { private val reader = new com.vividsolutions.jts.io.WKBReader() private val writer = new com.vividsolutions.jts.io.WKBWriter() - def read(source: Source): Geometry = - source { in => - val s = new com.vividsolutions.jts.io.InputStreamInStream(in) - reader.read(s) - } - - def write[T](geom: Geometry, sink: Sink[T]): T = - sink { out => - val s = new com.vividsolutions.jts.io.OutputStreamOutStream(out) - writer.write(geom, s) - } + def decodeFrom(in: java.io.InputStream): Geometry = + reader.read(new com.vividsolutions.jts.io.InputStreamInStream(in)) + + def encodeTo(out: java.io.OutputStream, g: Geometry) { + writer.write(g, new com.vividsolutions.jts.io.OutputStreamOutStream(out)) + } } import org.geotools.geojson.geom.GeometryJSON class GeoJSON(format: GeometryJSON) extends Format[Geometry] { def this(precision: Int) = this(new GeometryJSON(precision)) - def read(source: Source): Geometry = source { format.read(_) } - def write[T](g: Geometry, sink: Sink[T]): T = sink { format.write(g, _) } + + def readFrom(source: java.io.Reader) = format.read(source) + + def writeTo(sink: java.io.Writer, g: Geometry): Unit = + format.write(g, sink) } object GeoJSON extends GeoJSON(new GeometryJSON) -import org.geotools.xml.{ Parser, Encoder } -import org.geotools.gml2 +object GML extends Encoder[Geometry] { + import org.geotools.xml.{ Parser, Encoder } + import org.geotools.gml2 -object GML extends Writer[Geometry] { - def write[T](g: Geometry, sink: Sink[T]): T = { + def encodeTo(sink: java.io.OutputStream, g: Geometry) { val configuration = new gml2.GMLConfiguration val encoder = new Encoder(configuration) val nsUri = configuration.getNamespaceURI val qname = new javax.xml.namespace.QName(nsUri, g.getGeometryType) - sink { encoder.encode(g, qname, _) } + encoder.encode(g, qname, sink) } } diff --git a/geoscript/src/main/scala/serialize/package.scala b/geoscript/src/main/scala/serialize/package.scala index d3cade7..1e3d6c3 100644 --- a/geoscript/src/main/scala/serialize/package.scala +++ b/geoscript/src/main/scala/serialize/package.scala @@ -1,103 +1,152 @@ -package org.geoscript -package serialize +package org.geoscript.serialize -import java.io.{ File, InputStream, OutputStream } +import java.io.File trait Reader[+T] { - def read(source: Source): T + def read[U : Readable](source: U): T = + implicitly[Readable[U]].read(source)(readFrom(_)) + def readFrom(source: java.io.Reader): T } trait Writer[-T] { - def write[A](t: T, sink: Sink[A]): A -} + def format(t: T): String = write(t, ())(Writable.writeString) -trait Format[T] extends Reader[T] with Writer[T] + def write[Spec, Out](t: T, spec: Spec)(implicit writable: Writable[Spec, Out]): Out = + implicitly[Writable[Spec, Out]].write(spec)(writeTo(_, t)) -trait Sink[T] { - def apply(op: OutputStream => Unit): T + def writeTo(sink: java.io.Writer, t: T): Unit } -object Sink { - implicit def stream(out: OutputStream): Sink[Unit] = - new Sink[Unit] { - def apply(op: OutputStream => Unit) = op(out) - } +trait Format[T] extends Reader[T] with Writer[T] - implicit def file(name: String): Sink[File] = - file(new java.io.File(name)) +trait Readable[T] { + def read[U](t: T)(op: java.io.Reader => U): U +} - implicit def file(file: File): Sink[File] = - new Sink[java.io.File] { - def apply(op: OutputStream => Unit) = { - val output = - new java.io.BufferedOutputStream(new java.io.FileOutputStream(file)) - stream(output)(op) - output.close() +trait Writable[Spec, Out] { + def write(spec: Spec)(op: java.io.Writer => Unit): Out +} - file +object Writable { + implicit object writeWriter extends Writable[java.io.Writer, Unit] { + def write(spec: java.io.Writer)(op: java.io.Writer => Unit): Unit = op(spec) + } + + implicit object writeFile extends Writable[File, Unit] { + def write(spec: File)(op: java.io.Writer => Unit): Unit = { + val writer = new java.io.FileWriter(spec) + try + op(writer) + finally + writer.close() + } + } + + implicit object writeString extends Writable[Unit, String] { + def write(spec: Unit)(op: java.io.Writer => Unit): String = { + val writer = new java.io.StringWriter + try { + op(writer) + writer.toString + } finally { + writer.close() } } + } +} - def string: Sink[String] = - new Sink[String] { // TODO: Needs better text support - def apply(op: OutputStream => Unit) = - buffer(op).view.map(_.toChar).mkString +object Readable { + implicit object readReader extends Readable[java.io.Reader] { + def read[T](in: java.io.Reader)(f: java.io.Reader => T): T = f(in) + } + + implicit object readFile extends Readable[File] { + def read[T](file: File)(f: java.io.Reader => T): T = { + val in = new java.io.BufferedReader(new java.io.FileReader(file)) + try + f(in) + finally + in.close() } - - def buffer: Sink[Array[Byte]] = - new Sink[Array[Byte]] { - def apply(op: OutputStream => Unit) = { - val output = new java.io.ByteArrayOutputStream - stream(output)(op) - output.close() - output.toByteArray - } + } + + implicit object readString extends Readable[String] { + def read[T](s: String)(f: java.io.Reader => T): T = { + val in = new java.io.StringReader(s) + try + f(in) + finally + in.close() } + } } -trait Source { - def apply[T](op: InputStream => T): T +trait Decoder[+T] { + def decode[U : Decodable](source: U): T = + implicitly[Decodable[U]].decode(source)(decodeFrom(_)) + def decodeFrom(source: java.io.InputStream): T } -object Source { - implicit def stream(in: InputStream): Source = - new Source { - def apply[T](op: InputStream => T): T = op(in) - } +trait Encoder[-T] { + def buffer(t: T): Array[Byte] = encode(t, ())(Encodable.encodeBytes) - implicit def file(name: String): Source = - file(new java.io.File(name)) + def format(t: T): String = new String(buffer(t)) - implicit def file(file: File): Source = - new Source { - def apply[T](op: InputStream => T): T = { - val input = - new java.io.BufferedInputStream(new java.io.FileInputStream(file)) - val res = stream(input)(op) - input.close() + def encode[Spec, Out](t: T, spec: Spec)(implicit encodable: Encodable[Spec, Out]): Out = + implicitly[Encodable[Spec, Out]].encode(spec)(encodeTo(_, t)) - res - } - } + def encodeTo(sink: java.io.OutputStream, t: T): Unit +} - def string(data: String): Source = - new Source { - def apply[T](op: InputStream => T): T = { - val input = new java.io.ByteArrayInputStream(data.getBytes()) - val res = stream(input)(op) - input.close() - res - } - } +trait Codec[T] extends Encoder[T] with Decoder[T] - def buffer(data: Array[Byte]): Source = - new Source { - def apply[T](op: InputStream => T): T = { - val input = new java.io.ByteArrayInputStream(data) - val res = stream(input)(op) - input.close() +trait Decodable[T] { + def decode[U](t: T)(op: java.io.InputStream => U): U +} - res - } +trait Encodable[Spec, Out] { + def encode(spec: Spec)(op: java.io.OutputStream => Unit): Out +} + +object Encodable { + implicit object encodeOutputStream extends Encodable[java.io.OutputStream, Unit] { + def encode(spec: java.io.OutputStream)(op: java.io.OutputStream => Unit): Unit = op(spec) + } + + implicit object encodeBytes extends Encodable[Unit, Array[Byte]] { + def encode(spec: Unit)(op: java.io.OutputStream => Unit): Array[Byte] = { + val buff = new java.io.ByteArrayOutputStream + try { + op(buff) + buff.toByteArray + } finally + buff.close() + } + } +} + +object Decodable { + implicit object decodeDecoder extends Decodable[java.io.InputStream] { + def decode[T](in: java.io.InputStream)(f: java.io.InputStream => T): T = f(in) + } + + implicit object decodeFile extends Decodable[File] { + def decode[T](file: File)(f: java.io.InputStream => T): T = { + val in = new java.io.BufferedInputStream(new java.io.FileInputStream(file)) + try + f(in) + finally + in.close() + } + } + + implicit object decodeString extends Decodable[Array[Byte]] { + def decode[T](bs: Array[Byte])(f: java.io.InputStream => T): T = { + val in = new java.io.ByteArrayInputStream(bs) + try + f(in) + finally + in.close() } + } } diff --git a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index fa50cff..4b65428 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -1,22 +1,21 @@ package org.geoscript package geometry -import org.geoscript.serialize.{ Sink, Source } import org.scalatest._, matchers._ class SerializationSpec extends FunSuite with ShouldMatchers { test("round-trip points") { val p = point(100, 0) - val json = GeoJSON.write(p, Sink.string) + val json = GeoJSON.format(p) json should be("""{"type":"Point","coordinates":[100,0.0]}""") - GeoJSON.read(Source.string(json)) should be(p) + GeoJSON.read(json) should be(p) // TODO: Implement equality for geometries } test("round-trip linestrings") { val ls = lineString(Seq((100, 0), (101, 1))) - GeoJSON.write(ls, Sink.string) should be - ("""{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""") + GeoJSON.format(ls) should be( + """{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""") } test("round-trip polygons") { @@ -31,15 +30,15 @@ class SerializationSpec extends FunSuite with ShouldMatchers { ) ) - GeoJSON.write(solid, Sink.string) should be( + GeoJSON.format(solid) should be( """{"type":"Polygon","coordinates":[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]]]}""") - GeoJSON.write(withHoles, Sink.string) should be( + GeoJSON.format(withHoles) should be( """{"type":"Polygon","coordinates":[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]}""") } test("round-trip a multipoint") { val mp = multiPoint(Seq((100.0, 0.0), (101.0, 1.0))) - GeoJSON.write(mp, Sink.string) should be( + GeoJSON.format(mp) should be( """{"type":"MultiPoint","coordinates":[[100,0.0],[101,1]]}""") } @@ -49,7 +48,7 @@ class SerializationSpec extends FunSuite with ShouldMatchers { Seq((102, 2), (103, 3)) )) - GeoJSON.write(mls, Sink.string) should be( + GeoJSON.format(mls) should be( """{"type":"MultiLineString","coordinates":[[[100,0.0],[101,1]],[[102,2],[103,3]]]}""") } @@ -59,13 +58,18 @@ class SerializationSpec extends FunSuite with ShouldMatchers { (Seq((100, 0), (101, 0), (101, 1), (100, 1), (100, 0)), Seq(Seq((100.2, 0.2), (100.8, 0.2), (100.8, 0.8), (100.2, 0.8), (100.2, 0.2)))))) - GeoJSON.write(mp, Sink.string) should be( + GeoJSON.format(mp) should be( """{"type":"MultiPolygon","coordinates":[[[[102,2],[103,2],[103,3],[102,3],[102,2]]],[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]],[[100.2,0.2],[100.8,0.2],[100.8,0.8],[100.2,0.8],[100.2,0.2]]]]}""") } test("round-trip a GeometryCollection") { val gc = multi(Seq(point(100, 0), lineString(Seq((101, 0), (102, 1))))) - GeoJSON.write(gc, Sink.string) should be( + GeoJSON.format(gc) should be( """{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[100,0.0]},{"type":"LineString","coordinates":[[101,0.0],[102,1]]}]}""") } + + test("We should be able to stream JSON straight to a file") { + val p = point(1, 2) + GeoJSON.write(p, new java.io.File("/home/dwins/Blub.json")) + } } diff --git a/geoscript/src/test/scala/tutorial/BasicGeometry.scala b/geoscript/src/test/scala/tutorial/BasicGeometry.scala index c70f9c4..b2ebef0 100644 --- a/geoscript/src/test/scala/tutorial/BasicGeometry.scala +++ b/geoscript/src/test/scala/tutorial/BasicGeometry.scala @@ -52,11 +52,10 @@ object BasicGeometry extends App { line.buffer(1) // serialization - import org.geoscript.serialize.{ Sink, Source } - geometry.WKT.write(point, Sink.string) - geometry.GeoJSON.write(point, Sink.string) + geometry.WKT.format(point) + geometry.GeoJSON.format(point) // deserialization - geometry.WKT.read(Source.string("POINT (30 10)")) - geometry.GeoJSON.read(Source.string("POINT (30 10)")) + geometry.WKT.read("POINT (30 10)") + geometry.GeoJSON.read("POINT (30 10)") } From 34ca0fa5302a4061337ed52d7dd1bc00f5b92d59 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 31 Jul 2012 22:07:07 -0400 Subject: [PATCH 39/53] Break serialize package up into multiple source files --- .../src/main/scala/serialize/Codec.scala | 21 +++ .../src/main/scala/serialize/Decodable.scala | 31 ++++ .../src/main/scala/serialize/Encodable.scala | 22 +++ .../src/main/scala/serialize/Readable.scala | 31 ++++ .../src/main/scala/serialize/Writable.scala | 33 +++++ .../src/main/scala/serialize/package.scala | 134 ------------------ 6 files changed, 138 insertions(+), 134 deletions(-) create mode 100644 geoscript/src/main/scala/serialize/Codec.scala create mode 100644 geoscript/src/main/scala/serialize/Decodable.scala create mode 100644 geoscript/src/main/scala/serialize/Encodable.scala create mode 100644 geoscript/src/main/scala/serialize/Readable.scala create mode 100644 geoscript/src/main/scala/serialize/Writable.scala diff --git a/geoscript/src/main/scala/serialize/Codec.scala b/geoscript/src/main/scala/serialize/Codec.scala new file mode 100644 index 0000000..939834f --- /dev/null +++ b/geoscript/src/main/scala/serialize/Codec.scala @@ -0,0 +1,21 @@ +package org.geoscript.serialize + + +trait Decoder[+T] { + def decode[U : Decodable](source: U): T = + implicitly[Decodable[U]].decode(source)(decodeFrom(_)) + def decodeFrom(source: java.io.InputStream): T +} + +trait Encoder[-T] { + def buffer(t: T): Array[Byte] = encode(t, ())(Encodable.encodeBytes) + + def format(t: T): String = new String(buffer(t)) + + def encode[Spec, Out](t: T, spec: Spec)(implicit encodable: Encodable[Spec, Out]): Out = + implicitly[Encodable[Spec, Out]].encode(spec)(encodeTo(_, t)) + + def encodeTo(sink: java.io.OutputStream, t: T): Unit +} + +trait Codec[T] extends Encoder[T] with Decoder[T] diff --git a/geoscript/src/main/scala/serialize/Decodable.scala b/geoscript/src/main/scala/serialize/Decodable.scala new file mode 100644 index 0000000..f434372 --- /dev/null +++ b/geoscript/src/main/scala/serialize/Decodable.scala @@ -0,0 +1,31 @@ +package org.geoscript.serialize + +trait Decodable[T] { + def decode[U](t: T)(op: java.io.InputStream => U): U +} + +object Decodable { + implicit object decodeDecoder extends Decodable[java.io.InputStream] { + def decode[T](in: java.io.InputStream)(f: java.io.InputStream => T): T = f(in) + } + + implicit object decodeFile extends Decodable[java.io.File] { + def decode[T](file: java.io.File)(f: java.io.InputStream => T): T = { + val in = new java.io.BufferedInputStream(new java.io.FileInputStream(file)) + try + f(in) + finally + in.close() + } + } + + implicit object decodeString extends Decodable[Array[Byte]] { + def decode[T](bs: Array[Byte])(f: java.io.InputStream => T): T = { + val in = new java.io.ByteArrayInputStream(bs) + try + f(in) + finally + in.close() + } + } +} diff --git a/geoscript/src/main/scala/serialize/Encodable.scala b/geoscript/src/main/scala/serialize/Encodable.scala new file mode 100644 index 0000000..241c8c5 --- /dev/null +++ b/geoscript/src/main/scala/serialize/Encodable.scala @@ -0,0 +1,22 @@ +package org.geoscript.serialize + +trait Encodable[Spec, Out] { + def encode(spec: Spec)(op: java.io.OutputStream => Unit): Out +} + +object Encodable { + implicit object encodeOutputStream extends Encodable[java.io.OutputStream, Unit] { + def encode(spec: java.io.OutputStream)(op: java.io.OutputStream => Unit): Unit = op(spec) + } + + implicit object encodeBytes extends Encodable[Unit, Array[Byte]] { + def encode(spec: Unit)(op: java.io.OutputStream => Unit): Array[Byte] = { + val buff = new java.io.ByteArrayOutputStream + try { + op(buff) + buff.toByteArray + } finally + buff.close() + } + } +} diff --git a/geoscript/src/main/scala/serialize/Readable.scala b/geoscript/src/main/scala/serialize/Readable.scala new file mode 100644 index 0000000..f709b31 --- /dev/null +++ b/geoscript/src/main/scala/serialize/Readable.scala @@ -0,0 +1,31 @@ +package org.geoscript.serialize + +trait Readable[T] { + def read[U](t: T)(op: java.io.Reader => U): U +} + +object Readable { + implicit object readReader extends Readable[java.io.Reader] { + def read[T](in: java.io.Reader)(f: java.io.Reader => T): T = f(in) + } + + implicit object readFile extends Readable[java.io.File] { + def read[T](file: java.io.File)(f: java.io.Reader => T): T = { + val in = new java.io.BufferedReader(new java.io.FileReader(file)) + try + f(in) + finally + in.close() + } + } + + implicit object readString extends Readable[String] { + def read[T](s: String)(f: java.io.Reader => T): T = { + val in = new java.io.StringReader(s) + try + f(in) + finally + in.close() + } + } +} diff --git a/geoscript/src/main/scala/serialize/Writable.scala b/geoscript/src/main/scala/serialize/Writable.scala new file mode 100644 index 0000000..d5bab62 --- /dev/null +++ b/geoscript/src/main/scala/serialize/Writable.scala @@ -0,0 +1,33 @@ +package org.geoscript.serialize + +trait Writable[Spec, Out] { + def write(spec: Spec)(op: java.io.Writer => Unit): Out +} + +object Writable { + implicit object writeWriter extends Writable[java.io.Writer, Unit] { + def write(spec: java.io.Writer)(op: java.io.Writer => Unit): Unit = op(spec) + } + + implicit object writeFile extends Writable[java.io.File, Unit] { + def write(spec: java.io.File)(op: java.io.Writer => Unit): Unit = { + val writer = new java.io.FileWriter(spec) + try + op(writer) + finally + writer.close() + } + } + + implicit object writeString extends Writable[Unit, String] { + def write(spec: Unit)(op: java.io.Writer => Unit): String = { + val writer = new java.io.StringWriter + try { + op(writer) + writer.toString + } finally { + writer.close() + } + } + } +} diff --git a/geoscript/src/main/scala/serialize/package.scala b/geoscript/src/main/scala/serialize/package.scala index 1e3d6c3..fffe603 100644 --- a/geoscript/src/main/scala/serialize/package.scala +++ b/geoscript/src/main/scala/serialize/package.scala @@ -1,7 +1,5 @@ package org.geoscript.serialize -import java.io.File - trait Reader[+T] { def read[U : Readable](source: U): T = implicitly[Readable[U]].read(source)(readFrom(_)) @@ -18,135 +16,3 @@ trait Writer[-T] { } trait Format[T] extends Reader[T] with Writer[T] - -trait Readable[T] { - def read[U](t: T)(op: java.io.Reader => U): U -} - -trait Writable[Spec, Out] { - def write(spec: Spec)(op: java.io.Writer => Unit): Out -} - -object Writable { - implicit object writeWriter extends Writable[java.io.Writer, Unit] { - def write(spec: java.io.Writer)(op: java.io.Writer => Unit): Unit = op(spec) - } - - implicit object writeFile extends Writable[File, Unit] { - def write(spec: File)(op: java.io.Writer => Unit): Unit = { - val writer = new java.io.FileWriter(spec) - try - op(writer) - finally - writer.close() - } - } - - implicit object writeString extends Writable[Unit, String] { - def write(spec: Unit)(op: java.io.Writer => Unit): String = { - val writer = new java.io.StringWriter - try { - op(writer) - writer.toString - } finally { - writer.close() - } - } - } -} - -object Readable { - implicit object readReader extends Readable[java.io.Reader] { - def read[T](in: java.io.Reader)(f: java.io.Reader => T): T = f(in) - } - - implicit object readFile extends Readable[File] { - def read[T](file: File)(f: java.io.Reader => T): T = { - val in = new java.io.BufferedReader(new java.io.FileReader(file)) - try - f(in) - finally - in.close() - } - } - - implicit object readString extends Readable[String] { - def read[T](s: String)(f: java.io.Reader => T): T = { - val in = new java.io.StringReader(s) - try - f(in) - finally - in.close() - } - } -} - -trait Decoder[+T] { - def decode[U : Decodable](source: U): T = - implicitly[Decodable[U]].decode(source)(decodeFrom(_)) - def decodeFrom(source: java.io.InputStream): T -} - -trait Encoder[-T] { - def buffer(t: T): Array[Byte] = encode(t, ())(Encodable.encodeBytes) - - def format(t: T): String = new String(buffer(t)) - - def encode[Spec, Out](t: T, spec: Spec)(implicit encodable: Encodable[Spec, Out]): Out = - implicitly[Encodable[Spec, Out]].encode(spec)(encodeTo(_, t)) - - def encodeTo(sink: java.io.OutputStream, t: T): Unit -} - -trait Codec[T] extends Encoder[T] with Decoder[T] - -trait Decodable[T] { - def decode[U](t: T)(op: java.io.InputStream => U): U -} - -trait Encodable[Spec, Out] { - def encode(spec: Spec)(op: java.io.OutputStream => Unit): Out -} - -object Encodable { - implicit object encodeOutputStream extends Encodable[java.io.OutputStream, Unit] { - def encode(spec: java.io.OutputStream)(op: java.io.OutputStream => Unit): Unit = op(spec) - } - - implicit object encodeBytes extends Encodable[Unit, Array[Byte]] { - def encode(spec: Unit)(op: java.io.OutputStream => Unit): Array[Byte] = { - val buff = new java.io.ByteArrayOutputStream - try { - op(buff) - buff.toByteArray - } finally - buff.close() - } - } -} - -object Decodable { - implicit object decodeDecoder extends Decodable[java.io.InputStream] { - def decode[T](in: java.io.InputStream)(f: java.io.InputStream => T): T = f(in) - } - - implicit object decodeFile extends Decodable[File] { - def decode[T](file: File)(f: java.io.InputStream => T): T = { - val in = new java.io.BufferedInputStream(new java.io.FileInputStream(file)) - try - f(in) - finally - in.close() - } - } - - implicit object decodeString extends Decodable[Array[Byte]] { - def decode[T](bs: Array[Byte])(f: java.io.InputStream => T): T = { - val in = new java.io.ByteArrayInputStream(bs) - try - f(in) - finally - in.close() - } - } -} From 7640a02b9a0e5688562023ced6a6661de399fef6 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Tue, 31 Jul 2012 22:07:49 -0400 Subject: [PATCH 40/53] Rename sources for Format trait --- .../src/main/scala/serialize/{package.scala => Format.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename geoscript/src/main/scala/serialize/{package.scala => Format.scala} (100%) diff --git a/geoscript/src/main/scala/serialize/package.scala b/geoscript/src/main/scala/serialize/Format.scala similarity index 100% rename from geoscript/src/main/scala/serialize/package.scala rename to geoscript/src/main/scala/serialize/Format.scala From 8d935c4da71781bc249902820e47160c73a891d1 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 5 Aug 2012 12:35:51 -0400 Subject: [PATCH 41/53] Charting and some tweaks to rendering API --- geoscript/build.sbt | 1 + geoscript/src/main/scala/Converters.scala | 9 +- .../src/main/scala/geometry/Geometry.scala | 32 ++++- .../src/main/scala/geometry/io/package.scala | 2 +- .../src/main/scala/geometry/package.scala | 1 + .../main/scala/projection/Projectable.scala | 13 +- .../main/scala/projection/Projection.scala | 15 +- .../main/scala/projection/Referenced.scala | 14 +- geoscript/src/main/scala/render/package.scala | 134 ++++++++++++++---- .../src/main/scala/serialize/Encodable.scala | 12 ++ 10 files changed, 185 insertions(+), 48 deletions(-) diff --git a/geoscript/build.sbt b/geoscript/build.sbt index 103c50f..8b36f3e 100644 --- a/geoscript/build.sbt +++ b/geoscript/build.sbt @@ -8,6 +8,7 @@ libraryDependencies <++= gtVersion { v => "org.geotools" % "gt-render" % v, "org.geotools" % "gt-xml" % v, "org.geotools" % "gt-geojson" % v, + "org.geotools" % "gt-charts" % v, "org.geotools.jdbc" % "gt-jdbc-postgis" % v, "org.geotools.jdbc" % "gt-jdbc-spatialite" % v ) diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 5741fb6..4fd3feb 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -5,7 +5,7 @@ package object geoscript { import layer._ import workspace._ import render._ - import projection.{ Projection, RichProjection } + import projection.{ Projection, RichProjection, Projectable, Referenced } implicit def enrichGeometry(g: Geometry): RichGeometry = new RichGeometry(g) @@ -37,4 +37,11 @@ package object geoscript { implicit def enrichProjection(p: Projection): RichProjection = new RichProjection(p) + + class RichProjectable[P : Projectable](p: P) { + def in(proj: Projection): Referenced[P] = Referenced(p, proj) + def lift: Option[Referenced[P]] = implicitly[Projectable[P]].lift(p) + } + + implicit def enrichProjectable[P : Projectable](p: P) = new RichProjectable(p) } diff --git a/geoscript/src/main/scala/geometry/Geometry.scala b/geoscript/src/main/scala/geometry/Geometry.scala index 7f98a16..c0cea21 100644 --- a/geoscript/src/main/scala/geometry/Geometry.scala +++ b/geoscript/src/main/scala/geometry/Geometry.scala @@ -9,22 +9,50 @@ class RichGeometry(geometry: Geometry) { */ def area: Double = geometry.getArea() + /** + * A symbolic alias for the union operation + */ + def ||(that: Geometry): Geometry = geometry union that + + /** + * A symbolic alias for the intersection operation + */ + def &&(that: Geometry): Geometry = geometry intersection that + /** * A jts.Envelope that fully encloses this Geometry. */ - def envelope: Envelope = geometry.getEnvelopeInternal() // in projection + def envelope: Envelope = geometry.getEnvelopeInternal() /** * A point that represents the "center of gravity" of this geometry's * enclosed area. Note that this point is not necessarily on the geometry! */ - def centroid: Point = geometry.getCentroid() // in projection + def centroid: Point = geometry.getCentroid() /** * All the coordinates that compose this Geometry as a sequence. */ def coordinates: Seq[Coordinate] = geometry.getCoordinates().toSeq + /** + * prepare creates a PreparedGeometry instance, which requires more time to + * construct but has faster implementations of several operations including + * `contains`, `coveredBy`, `covers`, `crosses`, `disjoint`, + * `intersects`, `overlaps`, `touches`, and `within` + * + * Typically, this would be used when one geometry is being compared against + * many others. + * + * {{{ + * (needle: Geometry, haystack: Geometry) => { + * val p = needle.prepared + * haystack.filter(p.contains(_)) + * } + * }}} + */ + def prepare = preparingFactory.create(geometry) + /** * The length of the line segments that compose this geometry, in the same * units as used by its coordinates. diff --git a/geoscript/src/main/scala/geometry/io/package.scala b/geoscript/src/main/scala/geometry/io/package.scala index 5b17017..36513cb 100644 --- a/geoscript/src/main/scala/geometry/io/package.scala +++ b/geoscript/src/main/scala/geometry/io/package.scala @@ -1,6 +1,7 @@ package org.geoscript.geometry import org.geoscript.serialize._ +import org.geotools.geojson.geom.GeometryJSON object WKT extends Format[Geometry] { private val reader = new com.vividsolutions.jts.io.WKTReader() @@ -23,7 +24,6 @@ object WKB extends Codec[Geometry] { } } -import org.geotools.geojson.geom.GeometryJSON class GeoJSON(format: GeometryJSON) extends Format[Geometry] { def this(precision: Int) = this(new GeometryJSON(precision)) diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index 055b25e..6330ee9 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -19,6 +19,7 @@ package object geometry { val EmptyEnvelope = new jts.Envelope val factory = new jts.GeometryFactory + protected[geometry] val preparingFactory = new jts.prep.PreparedGeometryFactory def union(a: Envelope, b: Envelope): Envelope = if (a.isNull) b diff --git a/geoscript/src/main/scala/projection/Projectable.scala b/geoscript/src/main/scala/projection/Projectable.scala index 1cf230b..6c6864b 100644 --- a/geoscript/src/main/scala/projection/Projectable.scala +++ b/geoscript/src/main/scala/projection/Projectable.scala @@ -2,21 +2,32 @@ package org.geoscript package projection import geometry._ -import org.geotools.geometry.jts.JTS +import org.geotools.geometry.jts.{ JTS, ReferencedEnvelope } trait Projectable[T] { + def extractProjection(t: T): Option[Projection] + def lift(t: T): Option[Referenced[T]] = + extractProjection(t).map(Referenced(t, _)(this)) def project(from: Projection, to: Projection)(t: T): T } object Projectable { implicit def geometriesAreProjectable[T <: Geometry]: Projectable[T] = new Projectable[T] { + def extractProjection(t: T): Option[Projection] = + if (t.getSRID > 0) + fromSrid("EPSG:" + t.getSRID) + else + None + def project(from: Projection, to: Projection)(t: T): T = JTS.transform(t, transform(from, to)).asInstanceOf[T] } implicit def envelopesAreProjectable: Projectable[Envelope] = new Projectable[Envelope] { + def extractProjection(e: Envelope): Option[Projection] = None + def project(from: Projection, to: Projection)(e: Envelope) = JTS.transform(e, transform(from, to)) } diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index 9a80f02..f5efb05 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -16,8 +16,15 @@ package object projection { type Projection = CoordinateReferenceSystem type Transform = org.opengis.referencing.operation.MathTransform + lazy val LatLon = fromSrid("EPSG:4326").get + lazy val WebMercator = fromSrid("EPSG:3857").get + private val catchLookup = catching(classOf[FactoryException]) + def forceXYAxisOrder() { + System.setProperty("org.geotools.referencing.forceXY", "true") + } + def fromWKT(s: String): Option[Projection] = catching(classOf[FactoryException]).opt { CRS.parseWKT(s) } @@ -35,14 +42,6 @@ package object projection { CRS.findMathTransform(p, q) def Projection(s: String): Projection = (fromSrid(s) orElse fromWKT(s)).orNull - - lazy val LatLon = fromSrid("EPSG:4326").get - lazy val WebMercator = fromSrid("EPSG:3857").get - - def reference[T : Projectable](t: T, proj: Projection): Referenced[T] = - Referenced(t, proj) - - def aspatial[T](t: T): Referenced[T] = Referenced(t) } package projection { diff --git a/geoscript/src/main/scala/projection/Referenced.scala b/geoscript/src/main/scala/projection/Referenced.scala index 6906de8..0e82ac4 100644 --- a/geoscript/src/main/scala/projection/Referenced.scala +++ b/geoscript/src/main/scala/projection/Referenced.scala @@ -8,8 +8,8 @@ trait Referenced[T] { def map[U](f: T => U): Referenced[U] def flatMap[U](f: T => Referenced[U]): Referenced[U] def native: Option[Projection] - def force(p: Projection): T - def forceNative: T = force(native.get) + def project(p: Projection): T + def forceNative: T = project(native.get) } object Referenced { @@ -21,7 +21,7 @@ object Referenced { private class Ideal[T](value: T) extends Referenced[T] { def map[U](f: T => U): Referenced[U] = new Ideal(f(value)) def flatMap[U](f: T => Referenced[U]): Referenced[U] = f(value) - def force(p: Projection): T = value + def project(p: Projection): T = value def native = None } @@ -33,7 +33,7 @@ object Referenced { def flatMap[U](f: T => Referenced[U]): Referenced[U] = new FlatMapped(this, f) - def force(p: Projection): T = + def project(p: Projection): T = implicitly[Projectable[T]].project(proj, p)(value) def native = Some(proj) @@ -46,7 +46,7 @@ object Referenced { def flatMap[V](g: U => Referenced[V]): Referenced[V] = new FlatMapped(base, f andThen g) - def force(p: Projection): U = f(base.force(p)) + def project(p: Projection): U = f(base.project(p)) def native = base.native } @@ -58,8 +58,8 @@ object Referenced { def flatMap[V](g: U => Referenced[V]): Referenced[V] = new FlatMapped(this, g) - def force(p: Projection): U = - f(base.force(p)).force(p) + def project(p: Projection): U = + f(base.project(p)).project(p) def native = base.native } diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index 34c439c..3627178 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -1,6 +1,6 @@ package org.geoscript -import style.combinators._, feature._, geometry._, projection._ +import style.combinators._, feature._, geometry._, projection._, serialize._ import collection.JavaConverters._ import org.{ geotools => gt } @@ -80,8 +80,7 @@ package render { } } - class ImageCanvas extends Canvas[awt.image.BufferedImage] { - val size = (256, 256) // TODO: Make this a constructor argument + class ImageCanvas(size: (Int, Int) = (256, 256)) extends Canvas[awt.image.BufferedImage] { override def render(draw: Draw): awt.image.BufferedImage = { val (w, h) = size val img = new java.awt.image.BufferedImage(w, h, @@ -129,6 +128,41 @@ package render { } class RichContent(content: Content) { + /** + * Calculate a bounding box for this Content that will include all data + * from all layers, in the projection used by the largest fraction of + * them. + */ + def calculatedBounds: ReferencedEnvelope = { + def freqs[A](xs: Seq[A]): Map[A, Int] = + (Map.empty[A, Int] /: xs) { + (accum, x) => accum + ((x, accum.getOrElse(x, 0) + 1)) + } + + def mode[A](xs: Seq[A]): Option[A] = + if (xs isEmpty) + None + else + Some(freqs(xs).toSeq.maxBy(_._2)._1) + + val expand = (a: Referenced[Envelope], b: Referenced[Envelope]) => + for (aEnv <- a; bEnv <- b) yield aEnv || bEnv + + val projection = + mode(content.layers.asScala.map(_.getBounds.getCoordinateReferenceSystem)) + .getOrElse(LatLon) + val boundsList = content.layers.asScala.map(_.getBounds) + val bounds = boundsList + .map(x => x: Referenced[Envelope]) + .reduceLeftOption { (refA, refB) => + for { a <- refA; b <- refB } yield a || b } + + bounds match { + case Some(bounds) => new ReferencedEnvelope(bounds.project(projection), projection) + case None => new ReferencedEnvelope(EmptyEnvelope, projection) + } + } + def withLayer(l: Layer): Content = { val newContent = new Content newContent.layers.addAll(content.layers) @@ -151,56 +185,100 @@ package object render { type StyleLayer = gt.map.Layer type DirectLayer = gt.map.DirectLayer + private def mkChart(geoms: Traversable[_ <: Geometry]) = { + import org.geotools.renderer.chart. { GeometryDataset, GeometryRenderer } + import org.jfree.chart + + val data = new GeometryDataset(geoms.toSeq: _*) + val renderer = new GeometryRenderer() + val xyplot = new chart.plot.XYPlot(data, data.getDomain(), data.getRange(), renderer) + new chart.JFreeChart(xyplot) + } + + def plot(geoms: Traversable[_ <: Geometry]) { + val chart = mkChart(geoms) + val chartPanel = new org.jfree.chart.ChartPanel(chart) + + val frame = new javax.swing.JFrame() + frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE) + frame.setContentPane(chartPanel) + frame.setSize(500, 500) + frame.setVisible(true) + } + + def plotOn[Out] + (geoms: Traversable[_ <: Geometry], + canvas: Canvas[Out] = new ImageCanvas) + : Out = { + val chart = mkChart(geoms) + canvas.render { (graphics, screenArea) => + chart.draw(graphics, tupleAsRectangle(screenArea)) + } + } + def applyStyle[T : Stylable](t: T, s: org.geoscript.style.Style): StyleLayer = implicitly[Stylable[T]].applyStyle(t, s) + type Frame = (Content, Dimension) => ReferencedEnvelope + val AutoStretch: Frame = (content, dimension) => content.calculatedBounds + def Stretch(re: Referenced[Envelope]): Frame = (_, _) => { + val prj = re.native.getOrElse(LatLon) + new ReferencedEnvelope(re project prj, prj) + } + def draw[Out]( content: Content, - bounds: ReferencedEnvelope, + frame: Frame = AutoStretch, + // bounds: Referenced[Envelope], canvas: Canvas[Out] = new ImageCanvas ): Out = { val hints = renderHints(KEY_ANTIALIASING -> VALUE_ANTIALIAS_ON) + // val proj = bounds.native getOrElse projection.LatLon + // val refEnv = new ReferencedEnvelope(bounds.project(proj), proj) canvas.render { (graphics, screenArea) => + val refEnv = frame(content, screenArea) val renderer = new gt.renderer.lite.StreamingRenderer() renderer.setJava2DHints(hints) renderer.setMapContent(content) - renderer.paint(graphics, tupleAsRectangle(screenArea), bounds) + renderer.paint(graphics, tupleAsRectangle(screenArea), refEnv) } } - def drawFull[Out]( - content: Content, - canvas: Canvas[Out] = new ImageCanvas - ): Out = { - def mode[A](xs: Seq[A]): Option[A] = - if (xs.isEmpty) None - else Some(xs.groupBy(identity).maxBy(_._2.size)._1) + // def drawFull[Out]( + // content: Content, + // canvas: Canvas[Out] = ImageCanvas + // ): Out = { + // def mode[A](xs: Seq[A]): Option[A] = + // if (xs.isEmpty) None + // else Some(xs.groupBy(identity).maxBy(_._2.size)._1) - val boundsList = content.layers.asScala.map(_.getBounds) - val projectionList = boundsList.map(_.getCoordinateReferenceSystem) + // val boundsList = content.layers.asScala.map(_.getBounds) + // val projectionList = boundsList.map(_.getCoordinateReferenceSystem) - val projection = mode(projectionList).getOrElse(LatLon) + // val projection = mode(projectionList).getOrElse(LatLon) - val expand = (a: Referenced[Envelope], b: Referenced[Envelope]) => - Referenced.envelope(for (aEnv <- a; bEnv <- b) yield aEnv || bEnv) + // val expand = (a: Referenced[Envelope], b: Referenced[Envelope]) => + // Referenced.envelope(for (aEnv <- a; bEnv <- b) yield aEnv || bEnv) - val bounds = (boundsList reduceLeftOption (expand(_, _))) + // val bounds = (boundsList reduceLeftOption (expand(_, _))) - val finalBounds = bounds - .map(Referenced.envelope(_)) - .getOrElse(new ReferencedEnvelope(EmptyEnvelope, LatLon)) + // val finalBounds = bounds + // .map(Referenced.envelope(_)) + // .getOrElse(new ReferencedEnvelope(EmptyEnvelope, LatLon)) - draw(content, finalBounds, canvas) - } + // draw(content, finalBounds, canvas) + // } def file(f: String) = new java.io.File(f) - def png(f: java.io.File): Canvas[java.io.File] = - new ImageCanvas().map { i => - javax.imageio.ImageIO.write(i, "png", f) - f - } + def png[Spec, Out] + (dest: Spec = (), size: (Int, Int) = (512, 512)) + (implicit encodable: Encodable[Spec, Out]) + : Canvas[Out] + = new ImageCanvas(size).map { + img => encodable.encode(dest) { + out => javax.imageio.ImageIO.write(img, "png", out) } } def png(f: String): Canvas[java.io.File] = png(file(f)) diff --git a/geoscript/src/main/scala/serialize/Encodable.scala b/geoscript/src/main/scala/serialize/Encodable.scala index 241c8c5..bd92276 100644 --- a/geoscript/src/main/scala/serialize/Encodable.scala +++ b/geoscript/src/main/scala/serialize/Encodable.scala @@ -9,6 +9,18 @@ object Encodable { def encode(spec: java.io.OutputStream)(op: java.io.OutputStream => Unit): Unit = op(spec) } + implicit object encodeFile extends Encodable[java.io.File, java.io.File] { + def encode(spec: java.io.File)(op: java.io.OutputStream => Unit): java.io.File = { + val out = new java.io.FileOutputStream(spec) + try { + op(out) + spec + } finally { + out.close() + } + } + } + implicit object encodeBytes extends Encodable[Unit, Array[Byte]] { def encode(spec: Unit)(op: java.io.OutputStream => Unit): Array[Byte] = { val buff = new java.io.ByteArrayOutputStream From c3176b819b21b12f20fb0524497b9d026268433e Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 5 Aug 2012 12:59:29 -0400 Subject: [PATCH 42/53] Add background color support for PNG canvas, fix example code --- geoscript/src/main/scala/render/package.scala | 7 ++++++- .../test/scala/tutorial/GeometryAdvanced.scala | 18 +++++++++--------- .../src/test/scala/tutorial/visualize.scala | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index 3627178..353b4e8 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -179,12 +179,17 @@ package render { package object render { type Content = gt.map.MapContent + type Color = java.awt.Color type Draw = (awt.Graphics2D, Dimension) => Unit type Dimension = (Int, Int) type Layer = gt.map.Layer type StyleLayer = gt.map.Layer type DirectLayer = gt.map.DirectLayer + val White = java.awt.Color.WHITE + val Black = java.awt.Color.BLACK + val Transparent = new java.awt.Color(0, 0, 0, 0) + private def mkChart(geoms: Traversable[_ <: Geometry]) = { import org.geotools.renderer.chart. { GeometryDataset, GeometryRenderer } import org.jfree.chart @@ -273,7 +278,7 @@ package object render { def file(f: String) = new java.io.File(f) def png[Spec, Out] - (dest: Spec = (), size: (Int, Int) = (512, 512)) + (dest: Spec = (), size: (Int, Int) = (512, 512), background: Color = Transparent) (implicit encodable: Encodable[Spec, Out]) : Canvas[Out] = new ImageCanvas(size).map { diff --git a/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala b/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala index 7099c66..1a14239 100644 --- a/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala +++ b/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala @@ -4,30 +4,30 @@ import org.geoscript._, geometry._, render._ object GeometryBuffer extends App { val poly = point(0, 0).buffer(1) - drawFull(Content(poly), new Window) + draw(Content(poly), canvas = new Window) } object GeometrySimplify extends App { val poly = point(0, 0).buffer(1) - drawFull(Content(simplify(poly, 0.05)), new Window) - drawFull(Content(simplify(poly, 0.1)), new Window) + draw(Content(simplify(poly, 0.05)), canvas = new Window) + draw(Content(simplify(poly, 0.1)), canvas = new Window) } object GeometryTransform extends App { val circle = point(0, 0).buffer(1) - drawFull( + draw( Content(Seq(circle, Transform.translated(dx=0.75, dy=0)(circle))), - new Window) + canvas = new Window) val box = polygon(Seq((0, 0), (1, 0), (1, 1), (0, 1), (0, 0))) - drawFull( + draw( Content(Seq( box, Transform.sheared(x=1, y=0).scaled(x=2, y=2)(box))), - new Window) + canvas = new Window) val bar = polygon(Seq((-5,-2),(5,-2),(5,2),(-5,2), (-5,-2))) val cross = bar union Transform.rotated(theta=math.toRadians(90))(bar) - drawFull( + draw( Content(Seq(cross, Transform.rotated(theta=math.toRadians(45))(cross))), - new Window) + canvas = new Window) } diff --git a/geoscript/src/test/scala/tutorial/visualize.scala b/geoscript/src/test/scala/tutorial/visualize.scala index ad5493f..a921b09 100644 --- a/geoscript/src/test/scala/tutorial/visualize.scala +++ b/geoscript/src/test/scala/tutorial/visualize.scala @@ -9,5 +9,5 @@ object Visualize extends App { Seq(Seq((20,30), (35,35), (30,20), (20,30)))) val bounds = new ReferencedEnvelope(poly.getEnvelopeInternal, projection.LatLon) val window = new Window - draw(Content(poly), bounds, window) + draw(Content(poly), Stretch(bounds), window) } From f0cc8bf1c13e705f05dd860393b19ced501f7dcc Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 5 Aug 2012 13:18:33 -0400 Subject: [PATCH 43/53] Get rid of Style wrapper trait --- geoscript/src/main/scala/render/package.scala | 2 +- geoscript/src/main/scala/style/CSS.scala | 86 +++++++++---------- geoscript/src/main/scala/style/Style.scala | 2 +- 3 files changed, 41 insertions(+), 49 deletions(-) diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index 353b4e8..369f24c 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -32,7 +32,7 @@ package render { implicit val vectorDataIsStylable: Stylable[layer.Layer] = new Stylable[layer.Layer] { def applyStyle(l: layer.Layer, s: style.Style): Layer = - new gt.map.FeatureLayer(l, s.underlying, l.name) + new gt.map.FeatureLayer(l, s, l.name) def defaultStyle(l: layer.Layer): Layer = { val Point = classOf[Point] diff --git a/geoscript/src/main/scala/style/CSS.scala b/geoscript/src/main/scala/style/CSS.scala index c84f96a..695645d 100644 --- a/geoscript/src/main/scala/style/CSS.scala +++ b/geoscript/src/main/scala/style/CSS.scala @@ -1,63 +1,55 @@ -package org.geoscript.style +package org.geoscript import org.geotools.{ styling => gt } import org.geotools.factory.CommonFactoryFinder.getStyleFactory -trait Style { - def underlying: gt.Style +package object style { + type Style = org.geotools.styling.Style } -class WrappedSLD(raw: gt.Style) extends Style { - def underlying = raw -} - -object SLD { - def fromFile(path: String): Style = { - val file = new java.io.File(path) - new WrappedSLD( +package style { + object SLD { + def fromFile(path: String): gt.Style = { + val file = new java.io.File(path) new gt.SLDParser(getStyleFactory(null), file).readXML()(0) - ) - } + } - def fromURL(url: String): Style = { - val resolved = new java.net.URL(new java.io.File(".").toURI.toURL, url) - new WrappedSLD( + def fromURL(url: String): gt.Style = { + val resolved = new java.net.URL(new java.io.File(".").toURI.toURL, url) new gt.SLDParser(getStyleFactory(null), url).readXML()(0) - ) - } + } - def fromString(sld: String): Style = { - val reader = new java.io.StringReader(sld) - new WrappedSLD( + def fromString(sld: String): gt.Style = { + val reader = new java.io.StringReader(sld) new gt.SLDParser(getStyleFactory(null), reader).readXML()(0) - ) - } - - def fromXML(sld: xml.Node): Style = { - val pprinter = new xml.PrettyPrinter(0, 0) - fromString(pprinter.format(sld)) - } -} - -object CSS { - import org.geoscript.geocss.CssParser.parse - val Translator = new org.geoscript.geocss.Translator - import Translator.css2sld - - def fromFile(path: String): Style = { - val reader = new java.io.FileReader(path) - val cssRules = parse(reader) - new WrappedSLD(css2sld(cssRules.get)) - } + } - def fromURL(url: String): Style = { - val resolved = new java.net.URL(new java.io.File(".").toURI.toURL, url) - val cssRules = parse(resolved.openStream) - new WrappedSLD(css2sld(cssRules.get)) + def fromXML(sld: xml.Node): gt.Style = { + val pprinter = new xml.PrettyPrinter(0, 0) + fromString(pprinter.format(sld)) + } } - def fromString(css: String): Style = { - val cssRules = parse(css) - new WrappedSLD(css2sld(cssRules.get)) + object CSS { + import org.geoscript.geocss.CssParser.parse + val Translator = new org.geoscript.geocss.Translator + import Translator.css2sld + + def fromFile(path: String): gt.Style = { + val reader = new java.io.FileReader(path) + val cssRules = parse(reader) + css2sld(cssRules.get) + } + + def fromURL(url: String): gt.Style = { + val resolved = new java.net.URL(new java.io.File(".").toURI.toURL, url) + val cssRules = parse(resolved.openStream) + css2sld(cssRules.get) + } + + def fromString(css: String): gt.Style = { + val cssRules = parse(css) + css2sld(cssRules.get) + } } } diff --git a/geoscript/src/main/scala/style/Style.scala b/geoscript/src/main/scala/style/Style.scala index 3276edd..03fd470 100644 --- a/geoscript/src/main/scala/style/Style.scala +++ b/geoscript/src/main/scala/style/Style.scala @@ -4,7 +4,7 @@ package style.combinators import filter._ import scala.collection.JavaConversions._ -sealed abstract trait Style extends style.Style { +sealed abstract trait Style { def where(filter: Filter): Style def aboveScale(s: Double): Style def belowScale(s: Double): Style From c5a179686e1fcd3467b07874154cfcc2136280a4 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 5 Aug 2012 14:24:30 -0400 Subject: [PATCH 44/53] Fix a null handling bug in Style.scala --- geoscript/src/main/scala/style/Style.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geoscript/src/main/scala/style/Style.scala b/geoscript/src/main/scala/style/Style.scala index 03fd470..3a9e2b0 100644 --- a/geoscript/src/main/scala/style/Style.scala +++ b/geoscript/src/main/scala/style/Style.scala @@ -1,7 +1,7 @@ -package org.geoscript -package style.combinators +package org.geoscript.style +package combinators -import filter._ +import org.geoscript.filter._ import scala.collection.JavaConversions._ sealed abstract trait Style { @@ -136,7 +136,7 @@ case class CompositeStyle(styles: Seq[Style]) extends Style { } object Paint { - import geocss.CssOps.colors + import org.geoscript.geocss.CssOps.colors implicit def stringToPaint(colorName: String): Paint = if (colors contains colorName) @@ -260,7 +260,7 @@ case class Label( val symbolizers = { val sym = factory.createTextSymbolizer() sym.setLabel(text) - sym.setGeometry(geometry) + sym.setGeometry(if (geometry == null) null else geometry) Seq(sym) } def zIndex = 0 From 707595d96f64efa1b1848ae240bf914c69b3a5b0 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 5 Aug 2012 14:25:02 -0400 Subject: [PATCH 45/53] Add WKT implementation of Format[Projection] --- .../src/main/scala/projection/Projection.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index f5efb05..6cd1e9f 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -45,7 +45,25 @@ package object projection { } package projection { + import org.geoscript.serialize._ + class RichProjection(p: Projection) { def id = CRS.lookupIdentifier(p, true) } + + object WKT extends Format[Projection] { + def readFrom(in: java.io.Reader): Projection = { + val accum = new StringBuilder() + val buff = Array.ofDim[Char](4096) + var len = 0 + while ({ len = in.read(buff) ; len >= 0 }) { + accum.appendAll(buff, 0, len) + } + CRS.parseWKT(accum.toString) + } + + def writeTo(out: java.io.Writer, p: Projection): Unit = { + out.write(p.toWKT) + } + } } From 262e1535b70372369e57dc7dcf8228669e6de49b Mon Sep 17 00:00:00 2001 From: David Winslow Date: Mon, 6 Aug 2012 08:46:13 -0400 Subject: [PATCH 46/53] Upate examples for API changes --- examples/src/main/scala/example/ColorRamp.scala | 2 +- examples/src/main/scala/example/Render.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/src/main/scala/example/ColorRamp.scala b/examples/src/main/scala/example/ColorRamp.scala index 9cf298c..705eff2 100644 --- a/examples/src/main/scala/example/ColorRamp.scala +++ b/examples/src/main/scala/example/ColorRamp.scala @@ -37,6 +37,6 @@ object ColorRamp extends org.geoscript.feature.GeoCrunch { filter = "%s BETWEEN %f AND %f".format(propertyName, min, max) } yield Fill(hex(color)) where cql(filter) - rules reduce (_ and _) + (rules reduce (_ and _)).underlying } } diff --git a/examples/src/main/scala/example/Render.scala b/examples/src/main/scala/example/Render.scala index ecbcfa4..3249287 100644 --- a/examples/src/main/scala/example/Render.scala +++ b/examples/src/main/scala/example/Render.scala @@ -1,7 +1,7 @@ package org.geoscript.example object Render extends App { - import org.geoscript._, render.{ Content, draw, png } + import org.geoscript._, render._ def reference(e: org.geoscript.geometry.Envelope, p: projection.Projection) = new org.geotools.geometry.jts.ReferencedEnvelope(e, p) @@ -15,7 +15,7 @@ object Render extends App { val theme = style.CSS.fromFile(styleFile) val bounds = reference(states.envelope, projection.Projection("EPSG:4326")) val win = new org.geoscript.render.Window - draw(Content(states, theme), bounds, win) + draw(Content(states, theme), Stretch(bounds), win) val watcher = new actors.DaemonActor { val styleFile = new java.io.File("../geocss/src/test/resources/states.css") @@ -26,7 +26,7 @@ object Render extends App { if (updated < lastModified) { try { val theme = style.CSS.fromFile("../geocss/src/test/resources/states.css") - draw(Content(states, theme), bounds, win) + draw(Content(states, theme), Stretch(bounds), win) } catch { case _ => () } From 7be368b248fadd80381155556c330f8d56afed5f Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sun, 12 Aug 2012 20:01:27 -0400 Subject: [PATCH 47/53] Add GML read support for Geometry --- geoscript/src/main/scala/geometry/io/package.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/geoscript/src/main/scala/geometry/io/package.scala b/geoscript/src/main/scala/geometry/io/package.scala index 36513cb..e1cbec2 100644 --- a/geoscript/src/main/scala/geometry/io/package.scala +++ b/geoscript/src/main/scala/geometry/io/package.scala @@ -35,7 +35,7 @@ class GeoJSON(format: GeometryJSON) extends Format[Geometry] { object GeoJSON extends GeoJSON(new GeometryJSON) -object GML extends Encoder[Geometry] { +object GML extends Codec[Geometry] { import org.geotools.xml.{ Parser, Encoder } import org.geotools.gml2 @@ -46,4 +46,10 @@ object GML extends Encoder[Geometry] { val qname = new javax.xml.namespace.QName(nsUri, g.getGeometryType) encoder.encode(g, qname, sink) } + + def decodeFrom(in: java.io.InputStream): Geometry = { + val configuration = new gml2.GMLConfiguration + val parser = new Parser(configuration, in) + parser.parse().asInstanceOf[Geometry] + } } From e50bf4fe229fc8f98b104ae45d00fdc9735766c6 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sat, 18 Aug 2012 19:45:14 -0400 Subject: [PATCH 48/53] Interim commit --- .../src/main/scala/feature/feature.scala | 225 +++++++++++++++++- geoscript/src/main/scala/filter/Filter.scala | 75 +++--- .../src/main/scala/geometry/Geometry.scala | 23 +- .../src/main/scala/geometry/package.scala | 5 + geoscript/src/main/scala/layer/package.scala | 89 ++----- .../main/scala/projection/HasProjection.scala | 49 ++++ .../main/scala/projection/Projectable.scala | 2 +- .../main/scala/projection/Projection.scala | 5 +- geoscript/src/main/scala/render/package.scala | 2 +- geoscript/src/main/scala/style/Style.scala | 6 +- .../src/main/scala/workspace/package.scala | 24 +- geoscript/src/test/scala/UsageTests.scala | 7 +- .../src/test/scala/tutorial/layers.scala | 8 +- 13 files changed, 378 insertions(+), 142 deletions(-) create mode 100644 geoscript/src/main/scala/projection/HasProjection.scala diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index 775e779..bd2776b 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -1,20 +1,18 @@ package org.geoscript +import geometry._ import org.opengis.feature.`type`.AttributeDescriptor import scala.collection.JavaConverters._ -package object feature { +package object feature extends org.geoscript.feature.LowPriorityImplicits { type Feature = org.opengis.feature.simple.SimpleFeature + type FeatureCollection = org.geotools.data.simple.SimpleFeatureCollection type Field = org.opengis.feature.`type`.AttributeDescriptor type GeoField = org.opengis.feature.`type`.GeometryDescriptor type Schema = org.opengis.feature.simple.SimpleFeatureType - def bind[T : Manifest](name: String): Field = { - val builder = new org.geotools.feature.AttributeTypeBuilder - builder.setName(name) - builder.setBinding(manifest[T].erasure) - builder.buildDescriptor(name, builder.buildType()) - } + def bind[T : Bindable](name: String): Field = + implicitly[Bindable[T]].bind(name) def bind[T <: geometry.Geometry : Manifest] (name: String, proj: projection.Projection): GeoField = { @@ -32,6 +30,13 @@ package object feature { builder.buildDescriptor(name, builder.buildType) } + def setSchemaName(name: String, schema: Schema): Schema = { + val builder = new org.geotools.feature.simple.SimpleFeatureTypeBuilder + builder.init(schema) + builder.setName(name) + builder.buildFeatureType + } + def fromAttributes(attributes: (String, Any)*): Feature = { val fields = attributes.map { case (n, v) => field(n, v.getClass) } val schema = Schema("internal", fields) @@ -47,9 +52,180 @@ package object feature { builder.set(idx, value) builder.buildFeature(null) } + + def next[A : Bindable]: PositionalFieldSet[A] = + new DirectPositionalFieldSet + + def pos[A : Bindable](index: Int): AbsoluteFieldSet[A] = new IndexedFieldSet(index) + + def named[A : Bindable](name: String): AbsoluteFieldSet[A] = + new NamedFieldSet(name) + + implicit def bindingSugar(name: String) = new { + def binds[T : Bindable] = named[T](name) + } + + implicit def combinesWithTilde[T](t: T) = new { + def ~[U,V](u: U)(implicit chain: TildeCombine[T,U,V]): V = chain(t, u) + } + + implicit def combinesWithChain[A, B, T[_] : TildeChainable] + : TildeCombine[T[A], T[B], T[A ~ B]] = + new TildeCombine[T[A], T[B], T[A ~ B]] { + def apply(a: T[A], b: T[B]): T[A ~ B] = + implicitly[TildeChainable[T]].chain(a,b) + } + + implicit object positionalFieldSetsAreChainable + extends TildeChainable[PositionalFieldSet] + { + def chain[A,B]( + a: PositionalFieldSet[A], + b: PositionalFieldSet[B] + ): PositionalFieldSet[A ~ B] = new CombinedPositionalFieldSet(a, b) + } + + implicit object absoluteFieldSetsAreChainable + extends TildeChainable[AbsoluteFieldSet] + { + def chain[A,B]( + a: AbsoluteFieldSet[A], + b: AbsoluteFieldSet[B] + ): AbsoluteFieldSet[A ~ B] = new CombinedAbsoluteFieldSet(a, b) + } + + implicit def asFeatureCollection(fs: Traversable[Feature]): FeatureCollection = { + import scala.collection.JavaConversions._ + new org.geotools.data.collection.ListFeatureCollection(fs.head.schema, fs.toSeq) + } + + implicit def bindGeometry[G <: Geometry : Manifest]: Bindable[G] = + new Bindable[G] { + def bind(name: String): Field = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.setName(name) + builder.setBinding(manifest[G].erasure) + // builder.setCRS(proj) + builder.buildDescriptor(name, builder.buildGeometryType()) + } + + def withDefaultName = bind(implicitly[Manifest[G]].erasure.getSimpleName) + } } package feature { + sealed class ~[A,B](val a: A, val b: B) { + override def toString = a + " ~ " + b + } + + object ~ { + def unapply[A,B](x: (A ~ B)): Some[(A,B)] = Some((x.a, x.b)) + } + + class TildeChainer[A, T[_]] private[feature](a: T[A]) { + def ~[B](b: T[B])(implicit chainable: TildeChainable[T]): T[A ~ B] = + implicitly[TildeChainable[T]].chain(a, b) + } + + trait TildeCombine[T, U, V] extends ((T, U) => V) + + trait TildeChainable[T[_]] { + def chain[A,B](a: T[A], b: T[B]): T[A ~ B] + } + + trait FieldSet[T] { + def apply(t: T): Feature = { + val schema = Schema("builder", toSeq) + val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) + build(builder, t) + builder.buildFeature(null) + } + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit + def unapply(ft: Feature): Option[T] + def toSeq: Seq[Field] + } + + sealed trait PositionalFieldSet[T] extends FieldSet[T] { self => + def unapply(xs: Feature): Some[T] = Some(extract(xs, 0)._1) + def extract(ft: Feature, idx: Int): (T, Int) + } + + private class DirectPositionalFieldSet[T : Bindable] extends PositionalFieldSet[T] { + def extract(ft: Feature, idx: Int): (T, Int) = + (ft.getAttribute(idx).asInstanceOf[T], idx + 1) + + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = { + builder.add(t) + } + + def toSeq: Seq[Field] = + Seq(implicitly[Bindable[T]].withDefaultName) + } + + private class CombinedPositionalFieldSet[T, U]( + exT: PositionalFieldSet[T], + exU: PositionalFieldSet[U]) + extends PositionalFieldSet[T ~ U] + { + override def extract(ft: Feature, idx: Int): (T ~ U, Int) = { + val (t, nextIndex) = exT.extract(ft, idx) + val (u, nextIndex2) = exU.extract(ft, nextIndex) + (new ~ (t, u), nextIndex2) + } + + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, v: T ~ U): Unit = { + val (t ~ u) = v + exT.build(builder, t) + exU.build(builder, u) + } + + def toSeq: Seq[Field] = + exT.toSeq ++ exU.toSeq + } + + sealed trait AbsoluteFieldSet[T] extends FieldSet[T] { + def unapply(xs: Feature): Some[T] = Some(extract(xs)) + def extract(xs: Feature): T + } + + class IndexedFieldSet[T : Bindable](index: Int) extends AbsoluteFieldSet[T] { + def extract(ft: Feature) = ft.getAttribute(index).asInstanceOf[T] + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = + builder.set(index, t) + def toSeq: Seq[Field] = + Seq(implicitly[Bindable[T]].withDefaultName) + } + + class NamedFieldSet[T : Bindable](name: String) extends AbsoluteFieldSet[T] { + def extract(ft: Feature) = ft.getAttribute(name).asInstanceOf[T] + + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = + builder.set(name, t) + + def toSeq: Seq[Field] = + Seq(implicitly[Bindable[T]].withDefaultName) + } + + class CombinedAbsoluteFieldSet[T, U]( + exT: AbsoluteFieldSet[T], + exU: AbsoluteFieldSet[U] + ) extends AbsoluteFieldSet[T ~ U]{ + def extract(ft: Feature): (T ~ U) = { + val t = exT.extract(ft) + val u = exU.extract(ft) + (new ~ (t, u)) + } + + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, v: T ~ U): Unit = { + val (t ~ u) = v + exT.build(builder, t) + exU.build(builder, u) + } + + def toSeq: Seq[Field] = + exT.toSeq ++ exU.toSeq + } + object Schema { def apply(name: String, fields: Seq[Field]): Schema = { val builder = new org.geotools.feature.simple.SimpleFeatureTypeBuilder @@ -66,6 +242,7 @@ package feature { def fields: Seq[Field] = schema.getAttributeDescriptors.asScala def geometry = schema.getGeometryDescriptor + def projection = schema.getGeometryDescriptor.getCoordinateReferenceSystem def get(name: String): Field = schema.getDescriptor(name) def get(index: Int): Field = schema.getDescriptor(index) def withName(name: String): Schema = Schema(name, fields) @@ -73,12 +250,22 @@ package feature { org.geoscript.feature.feature(schema, attributes) } + object Feature { + def unapplySeq(f: Feature): Option[Seq[Any]] = Some(f.getAttributes.asScala) + } + class RichFeature(feature: Feature) { def id: String = feature.getID def get[A](index: Int): A = feature.getAttribute(index).asInstanceOf[A] def get[A](key: String): A = feature.getAttribute(key).asInstanceOf[A] def geometry: org.geoscript.geometry.Geometry = feature.getDefaultGeometry.asInstanceOf[org.geoscript.geometry.Geometry] + def schema: Schema = feature.getFeatureType + def asMap: Map[String, Any] = + (feature.getProperties.asScala.map { p => + val name = p.getName.getLocalPart + (name, feature.getAttribute(name)) + })(collection.breakOut) } class RichField(field: Field) { @@ -94,4 +281,28 @@ package feature { builder.buildDescriptor(field.getName, builder.buildGeometryType()) } } + + trait Bindable[A] { + def bind(name: String): Field + def withDefaultName: Field + } + + trait LowPriorityImplicits { + implicit def bindAnything[A : Manifest]: Bindable[A] = + new Bindable[A] { + def bind(name: String): Field = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.setName(name) + builder.setBinding(manifest[A].erasure) + builder.buildDescriptor(name, builder.buildType()) + } + + def withDefaultName = bind(implicitly[Manifest[A]].erasure.getSimpleName) + } + + implicit def combinesDirectly[A,B]: TildeCombine[A,B,A ~ B] = + new TildeCombine[A,B,A ~ B] { + def apply(a: A, b: B): A ~ B = new ~ (a, b) + } + } } diff --git a/geoscript/src/main/scala/filter/Filter.scala b/geoscript/src/main/scala/filter/Filter.scala index 4a544a2..abcd08a 100644 --- a/geoscript/src/main/scala/filter/Filter.scala +++ b/geoscript/src/main/scala/filter/Filter.scala @@ -1,4 +1,4 @@ -package org.geoscript.filter +package org.geoscript import com.vividsolutions.jts.{geom=>jts} import org.{geotools => gt} @@ -6,38 +6,51 @@ import org.opengis.{filter => ogc} import org.geoscript.geometry.Geometry -trait Filter { - def underlying: ogc.Filter -} +package object filter { + type Filter = ogc.Filter + // type Expression = ogc.expression.Expression -object Filter { private val factory = gt.factory.CommonFactoryFinder.getFilterFactory2(null) - private class Wrapper(val underlying: ogc.Filter) extends Filter - - object Include extends Filter { - val underlying = ogc.Filter.INCLUDE - def unapply(f: Filter): Boolean = f.underlying == ogc.Filter.INCLUDE - } - - def intersects(geometry: Geometry): Filter = { - new Wrapper( - factory.intersects(null, factory.literal(geometry)) - ) - } + def cql(text: String): Filter = + gt.filter.text.ecql.ECQL.toFilter(text) - def id(ids: Seq[String]): Filter = { - val idSet = new java.util.HashSet[ogc.identity.Identifier]() - for (i <- ids) { idSet.add(factory.featureId(i)) } - new Wrapper(factory.id(idSet)) - } - - def or(filters: Seq[Filter]): Filter = { - val idList = new java.util.ArrayList[ogc.Filter]() - for (f <- filters) { idList.add(f.underlying) } - new Wrapper(factory.or(idList)) - } - - implicit def wrap(f: ogc.Filter): Filter = new Wrapper(f) - implicit def unwrapped(f: Filter): ogc.Filter = f.underlying + def cqlExpression(text: String) // : Expression = + = gt.filter.text.ecql.ECQL.toExpression(text) } + +// trait Filter { +// def underlying: ogc.Filter +// } +// +// object Filter { +// private val factory = gt.factory.CommonFactoryFinder.getFilterFactory2(null) +// +// private class Wrapper(val underlying: ogc.Filter) extends Filter +// +// object Include extends Filter { +// val underlying = ogc.Filter.INCLUDE +// def unapply(f: Filter): Boolean = f.underlying == ogc.Filter.INCLUDE +// } +// +// def intersects(geometry: Geometry): Filter = { +// new Wrapper( +// factory.intersects(null, factory.literal(geometry)) +// ) +// } +// +// def id(ids: Seq[String]): Filter = { +// val idSet = new java.util.HashSet[ogc.identity.Identifier]() +// for (i <- ids) { idSet.add(factory.featureId(i)) } +// new Wrapper(factory.id(idSet)) +// } +// +// def or(filters: Seq[Filter]): Filter = { +// val idList = new java.util.ArrayList[ogc.Filter]() +// for (f <- filters) { idList.add(f.underlying) } +// new Wrapper(factory.or(idList)) +// } +// +// implicit def wrap(f: ogc.Filter): Filter = new Wrapper(f) +// implicit def unwrapped(f: Filter): ogc.Filter = f.underlying +// } diff --git a/geoscript/src/main/scala/geometry/Geometry.scala b/geoscript/src/main/scala/geometry/Geometry.scala index c0cea21..995890b 100644 --- a/geoscript/src/main/scala/geometry/Geometry.scala +++ b/geoscript/src/main/scala/geometry/Geometry.scala @@ -22,36 +22,21 @@ class RichGeometry(geometry: Geometry) { /** * A jts.Envelope that fully encloses this Geometry. */ - def envelope: Envelope = geometry.getEnvelopeInternal() + def envelope: Envelope = geometry.getEnvelopeInternal() // in projection /** * A point that represents the "center of gravity" of this geometry's * enclosed area. Note that this point is not necessarily on the geometry! */ - def centroid: Point = geometry.getCentroid() + def centroid: Point = geometry.getCentroid() // in projection /** * All the coordinates that compose this Geometry as a sequence. */ def coordinates: Seq[Coordinate] = geometry.getCoordinates().toSeq - /** - * prepare creates a PreparedGeometry instance, which requires more time to - * construct but has faster implementations of several operations including - * `contains`, `coveredBy`, `covers`, `crosses`, `disjoint`, - * `intersects`, `overlaps`, `touches`, and `within` - * - * Typically, this would be used when one geometry is being compared against - * many others. - * - * {{{ - * (needle: Geometry, haystack: Geometry) => { - * val p = needle.prepared - * haystack.filter(p.contains(_)) - * } - * }}} - */ - def prepare = preparingFactory.create(geometry) + def simplify(tolerance: Double): Geometry = + com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier.simplify(geometry, tolerance) /** * The length of the line segments that compose this geometry, in the same diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index 6330ee9..90dc671 100644 --- a/geoscript/src/main/scala/geometry/package.scala +++ b/geoscript/src/main/scala/geometry/package.scala @@ -156,4 +156,9 @@ package geometry { def edges: Geometry def triangles: Geometry } + + object Envelope { + def unapply(e: Envelope): Option[(Double, Double, Double, Double)] = + Some((e.getMinX, e.getMinY, e.getMaxX, e.getMaxY)) + } } diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 0cbca10..904a8a7 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -21,85 +21,52 @@ package object layer { } def Layer(name: String, fs: Traversable[feature.Feature]): Layer = { - implicit def featureHasSchema(f: Feature): { def schema: Schema } = - new { - def schema = f.getFeatureType - } + val schemata = (fs map (_.getFeatureType)).toSet - implicit def widen(f0: Seq[Field], f1: Seq[Field]): Seq[Field] = { - val names0 = (f0 map (_.name)) - val names1 = (f1 map (_.name)) - val names = names0 ++ (names1.filterNot(names0.contains)) - val byName0 = (f0 map (x => (x.name, x))).toMap - val byName1 = (f1 map (x => (x.name, x))).toMap - - names.map { n => - ((byName0 get n), (byName1 get n)) match { - case (None, None) => - sys.error("Unexpected state... name " + n + "not found in either schema in widen method") - case (Some(a), None) => - a - case (None, Some(b)) => - b - case (Some(a), Some(b)) => - if (a != b) - sys.error("Incompatible field types: " + a + ", " + b) - else - a - } - } - } - val attributes = - (fs foldLeft Seq.empty[Field]) { - (fs: Seq[Field], f: Feature) => - widen(fs, f.schema.fields) - } - val schema: Schema = Schema(name, attributes) - sys.error("undefined") + // TODO: Maybe it would be cool to infer a more general schema from the layers + require(schemata.size == 1, "Can't mix schemas when creating a new layer") + + val layer = Layer(schemata.head) + layer ++= fs + layer } } package layer { - class RichLayer(layer: Layer) { + class RichLayer(layer: Layer) extends Traversable[Feature] { def count: Int = layer.getCount(new Query) def envelope: geometry.Envelope = layer.getBounds def name: String = layer.getName.getLocalPart def schema: feature.Schema = layer.getSchema - def withAll[A](f: Iterator[feature.Feature] => A): A = - this.withCollection(layer.getFeatures())(f) - private def withCollection[A] - (collection: org.geotools.data.simple.SimpleFeatureCollection) - (f: Iterator[feature.Feature] => A) - : A - = { - val iter = collection.features() - val features = new Iterator[feature.Feature] { - def hasNext: Boolean = iter.hasNext - def next: feature.Feature = iter.next + def foreach[T](f: Feature => T) { foreachInCollection(layer.getFeatures)(f) } + + def filtered(filt: Filter): Traversable[Feature] = + new Traversable[Feature] { + def foreach[T](func: Feature => T) { + foreachInCollection(layer.getFeatures(filt))(func) + } } - try - f(features) - finally - iter.close() - } - def withFiltered[A] - (filter: Filter) - (f: Iterator[feature.Feature] => A) - : A - = this.withCollection(layer.getFeatures(filter))(f) + private def foreachInCollection + (fc: org.geotools.data.simple.SimpleFeatureCollection) + (f: Feature => _): Unit + = { + val iter = fc.features() + try while (iter.hasNext) f(iter.next) + finally iter.close() + } def workspace: Workspace = layer.getDataStore().asInstanceOf[Workspace] def += (fs: feature.Feature*) = this ++= fs - def ++= (features: Iterable[feature.Feature]) { + def ++= (features: Traversable[feature.Feature]) { val tx = new org.geotools.data.DefaultTransaction layer.setTransaction(tx) try { val featureColl = org.geotools.feature.FeatureCollections.newCollection() - featureColl.addAll(features.asJavaCollection) + featureColl.addAll(features.toSeq.asJavaCollection) layer.addFeatures(featureColl) tx.commit() } catch { @@ -109,10 +76,6 @@ package layer { layer.setTransaction(org.geotools.data.Transaction.AUTO_COMMIT) } } - - def ++= (features: Iterator[feature.Feature]) { - this ++= features.toIterable - } } object Shapefile { @@ -122,7 +85,7 @@ package layer { def apply(path: String): Layer = apply(new java.io.File(path)) def apply(path: java.io.File): Layer = { val ws = workspace.Directory(path.getParent()) - ws.layerNamed(basename(path)) + ws(basename(path)) } } } diff --git a/geoscript/src/main/scala/projection/HasProjection.scala b/geoscript/src/main/scala/projection/HasProjection.scala new file mode 100644 index 0000000..04ae041 --- /dev/null +++ b/geoscript/src/main/scala/projection/HasProjection.scala @@ -0,0 +1,49 @@ +package org.geoscript +package projection + +trait HasProjection[G] { + def setProjection(p: Projection)(g: G): G +} + +object HasProjection { + import feature._ + + implicit object FeaturesHaveProjections extends HasProjection[Feature] { + def setProjection(p: Projection)(g: Feature): Feature = { + val it = Iterator(g) + val dummyIterator = new org.geotools.feature.FeatureIterator[Feature] { + def close() {} + def hasNext = it.hasNext + def next = it.next + } + + new org.geotools.data.crs.ReprojectFeatureIterator( + dummyIterator, + SchemataHaveProjections.setProjection(p)(g.schema), + transform(g.schema.projection, p) + ).next.asInstanceOf[Feature] + } + } + + implicit object GeoFieldsHaveProjections extends HasProjection[GeoField] { + def setProjection(p: Projection)(g: GeoField): GeoField = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.init(g) + builder.setCRS(p) + builder.buildDescriptor(g.getName.getLocalPart).asInstanceOf[GeoField] + } + } + + implicit object SchemataHaveProjections extends HasProjection[Schema] { + def setProjection(p: Projection)(g: Schema): Schema = { + val builder = new org.geotools.feature.simple.SimpleFeatureTypeBuilder + builder.init(g) + builder.setAttributes(null: java.util.List[Field]) + g.fields.foreach { + case (g: GeoField) => builder.add(projection.setProjection(p)(g)) + case (f: Field) => builder.add(f) + } + builder.buildFeatureType() + } + } +} diff --git a/geoscript/src/main/scala/projection/Projectable.scala b/geoscript/src/main/scala/projection/Projectable.scala index 6c6864b..4bf00e8 100644 --- a/geoscript/src/main/scala/projection/Projectable.scala +++ b/geoscript/src/main/scala/projection/Projectable.scala @@ -1,7 +1,7 @@ package org.geoscript package projection -import geometry._ +import feature._, geometry._ import org.geotools.geometry.jts.{ JTS, ReferencedEnvelope } trait Projectable[T] { diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index 6cd1e9f..f55ecfb 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -33,6 +33,9 @@ package object projection { CRS.decode(s) } + def setProjection[G : HasProjection](p: Projection)(g: G): G = + implicitly[HasProjection[G]].setProjection(p)(g) + def reproject[G : Projectable] (p: Projection, q: Projection) (g: G) @@ -48,7 +51,7 @@ package projection { import org.geoscript.serialize._ class RichProjection(p: Projection) { - def id = CRS.lookupIdentifier(p, true) + def id: Option[String] = Option(CRS.lookupIdentifier(p, true)) } object WKT extends Format[Projection] { diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index 369f24c..b3388af 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -56,7 +56,7 @@ package render { .getOrElse(defaultSchema) val store = new org.geotools.data.memory.MemoryDataStore(fs.toArray) - store.layerNamed(store.names.head) + store(store.names.head) } implicit val singleFeatureIsStylable: Stylable[Feature] = diff --git a/geoscript/src/main/scala/style/Style.scala b/geoscript/src/main/scala/style/Style.scala index 3a9e2b0..a86eefd 100644 --- a/geoscript/src/main/scala/style/Style.scala +++ b/geoscript/src/main/scala/style/Style.scala @@ -81,9 +81,9 @@ abstract class SimpleStyle extends Style { def underlying = { val rule = styles.createRule() - for (f <- filter) rule.setFilter(f.underlying) - for (s <- minScale) rule.setMinScaleDenominator(s) - for (s <- maxScale) rule.setMaxScaleDenominator(s) + filter.foreach { rule.setFilter } + minScale.foreach { rule.setMinScaleDenominator } + maxScale.foreach { rule.setMaxScaleDenominator } rule.symbolizers.addAll(symbolizers) val ftstyle = styles.createFeatureTypeStyle() diff --git a/geoscript/src/main/scala/workspace/package.scala b/geoscript/src/main/scala/workspace/package.scala index eb97659..e7fda8a 100644 --- a/geoscript/src/main/scala/workspace/package.scala +++ b/geoscript/src/main/scala/workspace/package.scala @@ -57,6 +57,7 @@ package workspace { def database( dbtype: String, database: String, + user: String = null, host: String = null, port: Int = -1, maxOpenPreparedStatements: Int = 0, @@ -71,6 +72,7 @@ package workspace { Map( DBTYPE.key -> Option(dbtype), DATABASE.key -> Option(database), + USER.key -> Option(user), HOST.key -> Option(host), PORT.key -> Some(port).filter(_ > 0), MAX_OPEN_PREPARED_STATEMENTS.key -> Some(maxOpenPreparedStatements).filter(_ > 0), @@ -86,8 +88,9 @@ package workspace { def postgis( database: String, - host: String = null, - port: Int = -1, + host: String = "localhost", + user: String = "postgres", + port: Int = 5432, maxOpenPreparedStatements: Int = 0, minConnections: Int = 0, maxConnections: Int = 0, @@ -100,8 +103,9 @@ package workspace { Map( DBTYPE.key -> Option("postgis"), DATABASE.key -> Option(database), + USER.key -> Option(user), HOST.key -> Option(host), - PORT.key -> Some(port).filter(_ > 0), + PORT.key -> Some(port), MAX_OPEN_PREPARED_STATEMENTS.key -> Some(maxOpenPreparedStatements).filter(_ > 0), MINCONN.key -> Some(minConnections).filter(_ > 0), MAXCONN.key -> Some(maxConnections).filter(_ > 0), @@ -120,16 +124,24 @@ package workspace { } class RichWorkspace(ws: Workspace) { + def apply(name: String): Layer = (ws getFeatureSource name).asInstanceOf[Layer] def count = ws.getTypeNames.length def create(schema: feature.Schema): Layer = { ws.createSchema(schema) - layerNamed(schema.name) + (ws getFeatureSource schema.name).asInstanceOf[Layer] } - def layerNamed(name: String): Layer = - ws.getFeatureSource(name).asInstanceOf[Layer] + def get(name: String): Option[Layer] = + if (names contains name) + Some((ws getFeatureSource name).asInstanceOf[Layer]) + else + None def names: Seq[String] = ws.getTypeNames + + def +=(layer: Layer): Unit = { + create(layer.schema) ++= layer + } } class RichConnector(connector: Connector) { diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 1d5f84b..8368fbd 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -54,9 +54,7 @@ class UsageTests extends FunSuite with ShouldMatchers { test("support search") { val shp = layer.Shapefile(statesPath) - shp.withAll { fs => - fs.find(_.id == "states.1") - } should be ('defined) + shp.find(_.id == "states.1") should be ('defined) } test("provide access to schema information") { @@ -98,8 +96,7 @@ class UsageTests extends FunSuite with ShouldMatchers { dummy.count should be (2) - dummy.withAll( features => - features.find(_.get[String]("name") == "New York")) should be ('defined) + dummy.find(_.get[String]("name") == "New York") should be ('defined) } def closeTo(d: Double, eps: Double): BeMatcher[Double] = diff --git a/geoscript/src/test/scala/tutorial/layers.scala b/geoscript/src/test/scala/tutorial/layers.scala index 08b9ab4..3a6ee82 100644 --- a/geoscript/src/test/scala/tutorial/layers.scala +++ b/geoscript/src/test/scala/tutorial/layers.scala @@ -4,9 +4,7 @@ object Layers extends App { import org.geoscript._, layer._ val lyr = Layer("foo", Seq(feature.bind[String]("text"))) lyr += feature.fromAttributes(("text", "hello")) - lyr.withAll { features => - for (feature <- features) { - println(feature.get[String]("text")) - } - } + + for (feature <- lyr) + println(feature.get[String]("text")) } From c476095b5200bdd9397fda3b86d6a77629ec1f3d Mon Sep 17 00:00:00 2001 From: David Winslow Date: Sat, 18 Aug 2012 20:47:17 -0400 Subject: [PATCH 49/53] Make 'named' fieldsets use specified name --- geoscript/src/main/scala/feature/feature.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index bd2776b..56735ba 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -203,7 +203,7 @@ package feature { builder.set(name, t) def toSeq: Seq[Field] = - Seq(implicitly[Bindable[T]].withDefaultName) + Seq(implicitly[Bindable[T]].bind(name)) } class CombinedAbsoluteFieldSet[T, U]( From 753538a1b1e4c29dd2ba88d63b03f04447671d46 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Wed, 29 Aug 2012 21:17:07 -0400 Subject: [PATCH 50/53] Fleshing out some of the API while playing with databases --- .../src/main/scala/feature/feature.scala | 62 ++++++++++--------- geoscript/src/main/scala/layer/package.scala | 6 +- .../src/main/scala/workspace/package.scala | 2 + 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index 56735ba..618c388 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -56,7 +56,7 @@ package object feature extends org.geoscript.feature.LowPriorityImplicits { def next[A : Bindable]: PositionalFieldSet[A] = new DirectPositionalFieldSet - def pos[A : Bindable](index: Int): AbsoluteFieldSet[A] = new IndexedFieldSet(index) + def pos[A : Bindable](index: Int): FieldSet[A] = new IndexedFieldSet(index) def named[A : Bindable](name: String): AbsoluteFieldSet[A] = new NamedFieldSet(name) @@ -134,15 +134,8 @@ package feature { } trait FieldSet[T] { - def apply(t: T): Feature = { - val schema = Schema("builder", toSeq) - val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) - build(builder, t) - builder.buildFeature(null) - } - def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit def unapply(ft: Feature): Option[T] - def toSeq: Seq[Field] + // def toSeq: Seq[Field] } sealed trait PositionalFieldSet[T] extends FieldSet[T] { self => @@ -154,10 +147,6 @@ package feature { def extract(ft: Feature, idx: Int): (T, Int) = (ft.getAttribute(idx).asInstanceOf[T], idx + 1) - def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = { - builder.add(t) - } - def toSeq: Seq[Field] = Seq(implicitly[Bindable[T]].withDefaultName) } @@ -172,23 +161,10 @@ package feature { val (u, nextIndex2) = exU.extract(ft, nextIndex) (new ~ (t, u), nextIndex2) } - - def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, v: T ~ U): Unit = { - val (t ~ u) = v - exT.build(builder, t) - exU.build(builder, u) - } - - def toSeq: Seq[Field] = - exT.toSeq ++ exU.toSeq - } - - sealed trait AbsoluteFieldSet[T] extends FieldSet[T] { - def unapply(xs: Feature): Some[T] = Some(extract(xs)) - def extract(xs: Feature): T } - class IndexedFieldSet[T : Bindable](index: Int) extends AbsoluteFieldSet[T] { + class IndexedFieldSet[T : Bindable](index: Int) extends FieldSet[T] { + def unapply(ft: Feature) = Some(extract(ft)) def extract(ft: Feature) = ft.getAttribute(index).asInstanceOf[T] def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = builder.set(index, t) @@ -196,6 +172,20 @@ package feature { Seq(implicitly[Bindable[T]].withDefaultName) } + sealed trait AbsoluteFieldSet[T] extends FieldSet[T] { + def apply(t: T): Feature = { + val schema = Schema("builder", this) + val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) + build(builder, t) + builder.buildFeature(null) + } + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit + def extract(xs: Feature): T + def unapply(xs: Feature): Some[T] = Some(extract(xs)) + + def toSeq: Seq[Field] + } + class NamedFieldSet[T : Bindable](name: String) extends AbsoluteFieldSet[T] { def extract(ft: Feature) = ft.getAttribute(name).asInstanceOf[T] @@ -222,11 +212,13 @@ package feature { exU.build(builder, u) } - def toSeq: Seq[Field] = - exT.toSeq ++ exU.toSeq + def toSeq: Seq[Field] = exT.toSeq ++ exU.toSeq } object Schema { + def apply(name: String, fields: AbsoluteFieldSet[_]): Schema = + apply(name, fields.toSeq) + def apply(name: String, fields: Seq[Field]): Schema = { val builder = new org.geotools.feature.simple.SimpleFeatureTypeBuilder builder.setName(name) @@ -241,6 +233,14 @@ package feature { def name: String = schema.getName.getLocalPart def fields: Seq[Field] = schema.getAttributeDescriptors.asScala + + def apply(values: Seq[Any]): Feature = { + val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) + for ((v, i) <- values.zipWithIndex) + builder.set(i, v) + builder.buildFeature(null) + } + def geometry = schema.getGeometryDescriptor def projection = schema.getGeometryDescriptor.getCoordinateReferenceSystem def get(name: String): Field = schema.getDescriptor(name) @@ -261,6 +261,8 @@ package feature { def geometry: org.geoscript.geometry.Geometry = feature.getDefaultGeometry.asInstanceOf[org.geoscript.geometry.Geometry] def schema: Schema = feature.getFeatureType + def attributes: Seq[Any] = + feature.getAttributes.asScala.toSeq def asMap: Map[String, Any] = (feature.getProperties.asScala.map { p => val name = p.getName.getLocalPart diff --git a/geoscript/src/main/scala/layer/package.scala b/geoscript/src/main/scala/layer/package.scala index 904a8a7..548348b 100644 --- a/geoscript/src/main/scala/layer/package.scala +++ b/geoscript/src/main/scala/layer/package.scala @@ -66,7 +66,11 @@ package layer { layer.setTransaction(tx) try { val featureColl = org.geotools.feature.FeatureCollections.newCollection() - featureColl.addAll(features.toSeq.asJavaCollection) + val schema = layer.getSchema + val adjusted = + for (f <- features.view) + yield schema(f.attributes) + featureColl.addAll(adjusted.toSeq.asJavaCollection) layer.addFeatures(featureColl) tx.commit() } catch { diff --git a/geoscript/src/main/scala/workspace/package.scala b/geoscript/src/main/scala/workspace/package.scala index e7fda8a..25f9df6 100644 --- a/geoscript/src/main/scala/workspace/package.scala +++ b/geoscript/src/main/scala/workspace/package.scala @@ -59,6 +59,7 @@ package workspace { database: String, user: String = null, host: String = null, + password: String = null, port: Int = -1, maxOpenPreparedStatements: Int = 0, minConnections: Int = 0, @@ -75,6 +76,7 @@ package workspace { USER.key -> Option(user), HOST.key -> Option(host), PORT.key -> Some(port).filter(_ > 0), + PASSWD.key -> Option(password), MAX_OPEN_PREPARED_STATEMENTS.key -> Some(maxOpenPreparedStatements).filter(_ > 0), MINCONN.key -> Some(minConnections).filter(_ > 0), MAXCONN.key -> Some(maxConnections).filter(_ > 0), From 3afe817be85973d3067584e2bc28a19a6ebd6a13 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Wed, 29 Aug 2012 22:53:07 -0400 Subject: [PATCH 51/53] Try to tidy up a bit in feature and projection packages --- .../src/main/scala/feature/bindings.scala | 46 ------ .../src/main/scala/feature/feature.scala | 138 +++++------------- .../main/scala/projection/HasProjection.scala | 13 +- .../main/scala/projection/Projectable.scala | 4 +- .../main/scala/projection/Projection.scala | 20 ++- geoscript/src/main/scala/render/package.scala | 3 +- geoscript/src/test/scala/UsageTests.scala | 6 +- .../org/geoscript/workspaces/MemorySpec.scala | 10 +- .../src/test/scala/tutorial/layers.scala | 6 +- 9 files changed, 75 insertions(+), 171 deletions(-) delete mode 100644 geoscript/src/main/scala/feature/bindings.scala diff --git a/geoscript/src/main/scala/feature/bindings.scala b/geoscript/src/main/scala/feature/bindings.scala deleted file mode 100644 index 99df32a..0000000 --- a/geoscript/src/main/scala/feature/bindings.scala +++ /dev/null @@ -1,46 +0,0 @@ -package org.geoscript.feature - -@annotation.implicitNotFound( - "No geometry representation found for ${G}" -) -trait BoundGeometry[G] { - def binding: Class[_] -} - -object BoundGeometry { - import com.vividsolutions.jts.{ geom => jts } - - private def bind[G](c: Class[G]): BoundGeometry[G] = - new BoundGeometry[G] { def binding = c } - - implicit val boundPoint = bind(classOf[jts.Point]) - implicit val boundMultiPoint = bind(classOf[jts.MultiPoint]) - implicit val boundLineString = bind(classOf[jts.LineString]) - implicit val boundMultiLinestring = bind(classOf[jts.MultiLineString]) - implicit val boundPolygon = bind(classOf[jts.Polygon]) - implicit val boundMultiPolygon = bind(classOf[jts.MultiPolygon]) - implicit val boundGeometry = bind(classOf[jts.Geometry]) - implicit val boundGeometryCollection = bind(classOf[jts.GeometryCollection]) -} - -@annotation.implicitNotFound( - "No scalar representation found for ${S} (did you forget the projection for your geometry?)" -) -trait BoundScalar[S] { - def binding: Class[_] -} - -object BoundScalar { - private def bind[S](c: Class[S]): BoundScalar[S] = - new BoundScalar[S] { def binding = c } - - implicit val boundBoolean = bind(classOf[java.lang.Boolean]) - implicit val boundByte = bind(classOf[java.lang.Byte]) - implicit val boundShort = bind(classOf[java.lang.Short]) - implicit val boundInteger = bind(classOf[java.lang.Integer]) - implicit val boundLong = bind(classOf[java.lang.Long]) - implicit val boundFloat = bind(classOf[java.lang.Float]) - implicit val boundDouble = bind(classOf[java.lang.Double]) - implicit val boundString = bind(classOf[String]) - implicit val boundDate = bind(classOf[java.util.Date]) -} diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index 618c388..dc9d009 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -11,23 +11,11 @@ package object feature extends org.geoscript.feature.LowPriorityImplicits { type GeoField = org.opengis.feature.`type`.GeometryDescriptor type Schema = org.opengis.feature.simple.SimpleFeatureType - def bind[T : Bindable](name: String): Field = - implicitly[Bindable[T]].bind(name) - - def bind[T <: geometry.Geometry : Manifest] - (name: String, proj: projection.Projection): GeoField = { - val builder = new org.geotools.feature.AttributeTypeBuilder - builder.setName(name) - builder.setBinding(manifest[T].erasure) - builder.setCRS(proj) - builder.buildDescriptor(name, builder.buildGeometryType()) - } - - private def field(name: String, binding: Class[_]): Field = { + def field(name: String, binding: Class[_]): Field = { val builder = new org.geotools.feature.AttributeTypeBuilder builder.setName(name) builder.setBinding(binding) - builder.buildDescriptor(name, builder.buildType) + builder.buildDescriptor(name, builder.buildType()) } def setSchemaName(name: String, schema: Schema): Schema = { @@ -45,54 +33,14 @@ package object feature extends org.geoscript.feature.LowPriorityImplicits { builder.buildFeature(null) } - def feature(schema: Schema, attributes: Seq[Any]): Feature = { - import org.geotools.feature.simple.SimpleFeatureBuilder - val builder = new SimpleFeatureBuilder(schema) - for ((value, idx) <- attributes.zipWithIndex) - builder.set(idx, value) - builder.buildFeature(null) - } - - def next[A : Bindable]: PositionalFieldSet[A] = - new DirectPositionalFieldSet - - def pos[A : Bindable](index: Int): FieldSet[A] = new IndexedFieldSet(index) - def named[A : Bindable](name: String): AbsoluteFieldSet[A] = new NamedFieldSet(name) - implicit def bindingSugar(name: String) = new { - def binds[T : Bindable] = named[T](name) - } - - implicit def combinesWithTilde[T](t: T) = new { - def ~[U,V](u: U)(implicit chain: TildeCombine[T,U,V]): V = chain(t, u) - } - - implicit def combinesWithChain[A, B, T[_] : TildeChainable] - : TildeCombine[T[A], T[B], T[A ~ B]] = - new TildeCombine[T[A], T[B], T[A ~ B]] { - def apply(a: T[A], b: T[B]): T[A ~ B] = - implicitly[TildeChainable[T]].chain(a,b) + implicit def bindingSugarForString(name: String) = + new { + def binds[T : Bindable] = named[T](name) } - implicit object positionalFieldSetsAreChainable - extends TildeChainable[PositionalFieldSet] - { - def chain[A,B]( - a: PositionalFieldSet[A], - b: PositionalFieldSet[B] - ): PositionalFieldSet[A ~ B] = new CombinedPositionalFieldSet(a, b) - } - - implicit object absoluteFieldSetsAreChainable - extends TildeChainable[AbsoluteFieldSet] - { - def chain[A,B]( - a: AbsoluteFieldSet[A], - b: AbsoluteFieldSet[B] - ): AbsoluteFieldSet[A ~ B] = new CombinedAbsoluteFieldSet(a, b) - } implicit def asFeatureCollection(fs: Traversable[Feature]): FeatureCollection = { import scala.collection.JavaConversions._ @@ -105,7 +53,6 @@ package object feature extends org.geoscript.feature.LowPriorityImplicits { val builder = new org.geotools.feature.AttributeTypeBuilder builder.setName(name) builder.setBinding(manifest[G].erasure) - // builder.setCRS(proj) builder.buildDescriptor(name, builder.buildGeometryType()) } @@ -133,46 +80,7 @@ package feature { def chain[A,B](a: T[A], b: T[B]): T[A ~ B] } - trait FieldSet[T] { - def unapply(ft: Feature): Option[T] - // def toSeq: Seq[Field] - } - - sealed trait PositionalFieldSet[T] extends FieldSet[T] { self => - def unapply(xs: Feature): Some[T] = Some(extract(xs, 0)._1) - def extract(ft: Feature, idx: Int): (T, Int) - } - - private class DirectPositionalFieldSet[T : Bindable] extends PositionalFieldSet[T] { - def extract(ft: Feature, idx: Int): (T, Int) = - (ft.getAttribute(idx).asInstanceOf[T], idx + 1) - - def toSeq: Seq[Field] = - Seq(implicitly[Bindable[T]].withDefaultName) - } - - private class CombinedPositionalFieldSet[T, U]( - exT: PositionalFieldSet[T], - exU: PositionalFieldSet[U]) - extends PositionalFieldSet[T ~ U] - { - override def extract(ft: Feature, idx: Int): (T ~ U, Int) = { - val (t, nextIndex) = exT.extract(ft, idx) - val (u, nextIndex2) = exU.extract(ft, nextIndex) - (new ~ (t, u), nextIndex2) - } - } - - class IndexedFieldSet[T : Bindable](index: Int) extends FieldSet[T] { - def unapply(ft: Feature) = Some(extract(ft)) - def extract(ft: Feature) = ft.getAttribute(index).asInstanceOf[T] - def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = - builder.set(index, t) - def toSeq: Seq[Field] = - Seq(implicitly[Bindable[T]].withDefaultName) - } - - sealed trait AbsoluteFieldSet[T] extends FieldSet[T] { + sealed trait AbsoluteFieldSet[T] { def apply(t: T): Feature = { val schema = Schema("builder", this) val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) @@ -183,9 +91,34 @@ package feature { def extract(xs: Feature): T def unapply(xs: Feature): Some[T] = Some(extract(xs)) + def ~[U](that: AbsoluteFieldSet[U]): AbsoluteFieldSet[T ~ U] = + new CombinedAbsoluteFieldSet[T, U](this, that) + def toSeq: Seq[Field] } + object AbsoluteFieldSet { + import projection._ + implicit def geometryFieldsHaveProjection[G](implicit ev: G <:< Geometry): HasProjection[AbsoluteFieldSet[G]] = + new HasProjection[AbsoluteFieldSet[G]] { + def setProjection(p: Projection)(x: AbsoluteFieldSet[G]): AbsoluteFieldSet[G] = + new AbsoluteFieldSet[G] { + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, g: G) = + x.build(builder, g) + + def extract(xs: Feature): G = x.extract(xs) + + def toSeq: Seq[Field] = x.toSeq.map { + case (gf: GeoField) => force(p, gf) + case _ => sys.error("Trying to set projection on aspatial field") + } + } + + def transform(p: Projection)(x: AbsoluteFieldSet[G]): AbsoluteFieldSet[G] = + setProjection(p)(x) + } + } + class NamedFieldSet[T : Bindable](name: String) extends AbsoluteFieldSet[T] { def extract(ft: Feature) = ft.getAttribute(name).asInstanceOf[T] @@ -246,8 +179,13 @@ package feature { def get(name: String): Field = schema.getDescriptor(name) def get(index: Int): Field = schema.getDescriptor(index) def withName(name: String): Schema = Schema(name, fields) - def feature(attributes: Seq[Any]): Feature = - org.geoscript.feature.feature(schema, attributes) + def feature(attributes: Seq[Any]): Feature = { + import org.geotools.feature.simple.SimpleFeatureBuilder + val builder = new SimpleFeatureBuilder(schema) + for ((value, idx) <- attributes.zipWithIndex) + builder.set(idx, value) + builder.buildFeature(null) + } } object Feature { diff --git a/geoscript/src/main/scala/projection/HasProjection.scala b/geoscript/src/main/scala/projection/HasProjection.scala index 04ae041..726e6b7 100644 --- a/geoscript/src/main/scala/projection/HasProjection.scala +++ b/geoscript/src/main/scala/projection/HasProjection.scala @@ -3,6 +3,7 @@ package projection trait HasProjection[G] { def setProjection(p: Projection)(g: G): G + def transform(p: Projection)(g: G): G } object HasProjection { @@ -10,6 +11,11 @@ object HasProjection { implicit object FeaturesHaveProjections extends HasProjection[Feature] { def setProjection(p: Projection)(g: Feature): Feature = { + val adjusted = SchemataHaveProjections.setProjection(p)(g.schema) + adjusted(g.attributes) + } + + def transform(p: Projection)(g: Feature): Feature = { val it = Iterator(g) val dummyIterator = new org.geotools.feature.FeatureIterator[Feature] { def close() {} @@ -20,7 +26,7 @@ object HasProjection { new org.geotools.data.crs.ReprojectFeatureIterator( dummyIterator, SchemataHaveProjections.setProjection(p)(g.schema), - transform(g.schema.projection, p) + lookupTransform(g.schema.projection, p) ).next.asInstanceOf[Feature] } } @@ -32,6 +38,8 @@ object HasProjection { builder.setCRS(p) builder.buildDescriptor(g.getName.getLocalPart).asInstanceOf[GeoField] } + + def transform(p: Projection)(g: GeoField): GeoField = setProjection(p)(g) } implicit object SchemataHaveProjections extends HasProjection[Schema] { @@ -40,10 +48,11 @@ object HasProjection { builder.init(g) builder.setAttributes(null: java.util.List[Field]) g.fields.foreach { - case (g: GeoField) => builder.add(projection.setProjection(p)(g)) + case (g: GeoField) => builder.add(force(p, g)) case (f: Field) => builder.add(f) } builder.buildFeatureType() } + def transform(p: Projection)(g: Schema): Schema = setProjection(p)(g) } } diff --git a/geoscript/src/main/scala/projection/Projectable.scala b/geoscript/src/main/scala/projection/Projectable.scala index 4bf00e8..ef2d4f5 100644 --- a/geoscript/src/main/scala/projection/Projectable.scala +++ b/geoscript/src/main/scala/projection/Projectable.scala @@ -21,7 +21,7 @@ object Projectable { None def project(from: Projection, to: Projection)(t: T): T = - JTS.transform(t, transform(from, to)).asInstanceOf[T] + JTS.transform(t, lookupTransform(from, to)).asInstanceOf[T] } implicit def envelopesAreProjectable: Projectable[Envelope] = @@ -29,6 +29,6 @@ object Projectable { def extractProjection(e: Envelope): Option[Projection] = None def project(from: Projection, to: Projection)(e: Envelope) = - JTS.transform(e, transform(from, to)) + JTS.transform(e, lookupTransform(from, to)) } } diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index f55ecfb..a367d11 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -33,15 +33,21 @@ package object projection { CRS.decode(s) } - def setProjection[G : HasProjection](p: Projection)(g: G): G = - implicitly[HasProjection[G]].setProjection(p)(g) + // NOTE: Some data types store projections but not coordinates (eg, Schema). + // For these, force and reproject are the same. - def reproject[G : Projectable] - (p: Projection, q: Projection) - (g: G) - : G = implicitly[Projectable[G]].project(p, q)(g) + // Update projection tracking information without modifying coordinates + def force[G : HasProjection](proj: Projection, g: G): G = + implicitly[HasProjection[G]].setProjection(proj)(g) - def transform(p: Projection, q: Projection): Transform = + // Reproject coordinate data and update projection tracking informationo + def reproject[G : HasProjection](proj: Projection, g: G): G = + implicitly[HasProjection[G]].transform(proj)(g) + + def transform[G : Projectable](source: Projection, dest: Projection, g: G): G = + implicitly[Projectable[G]].project(source, dest)(g) + + def lookupTransform(p: Projection, q: Projection): Transform = CRS.findMathTransform(p, q) def Projection(s: String): Projection = (fromSrid(s) orElse fromWKT(s)).orNull diff --git a/geoscript/src/main/scala/render/package.scala b/geoscript/src/main/scala/render/package.scala index b3388af..5bb99cf 100644 --- a/geoscript/src/main/scala/render/package.scala +++ b/geoscript/src/main/scala/render/package.scala @@ -17,8 +17,7 @@ package render { object Stylable { private val defaultSchema = - Schema("empty", Seq( - bind[Geometry]("the_geom", projection.LatLon))) + Schema("empty", "the_geom".binds[Geometry]) def by[T, U : Stylable](f: T => U): Stylable[T] = new Stylable[T] { diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index 8368fbd..c89ab92 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -9,7 +9,7 @@ class UsageTests extends FunSuite with ShouldMatchers { test("work like on the geoscript homepage") { var p = point(-111, 45.7) - var p2 = (projection.reproject(Projection("epsg:4326"), Projection("epsg:26912")))(p) + var p2 = transform(Projection("epsg:4326"), Projection("epsg:26912"), p) var poly = p.buffer(100) p2.x should be(closeTo(499999.0, 1)) @@ -79,9 +79,7 @@ class UsageTests extends FunSuite with ShouldMatchers { val mem = workspace.Memory() mem.names should be ('empty) var dummy = mem.create(Schema("dummy", - Seq( - bind[String]("name"), - bind[Geometry]("geom", Projection("EPSG:4326"))))) + "name".binds[String] ~ (force(LatLon, "geom".binds[Geometry])))) mem.names.length should be (1) dummy += feature.fromAttributes( diff --git a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index 6dd4447..559e61c 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -1,17 +1,17 @@ package org.geoscript package workspace -import projection.Projection, feature.bind +import feature._, projection._ import org.scalatest._, matchers._ import com.vividsolutions.jts.geom.Point class MemorySpec extends FunSuite with ShouldMatchers { test("be able to create layers") { - val schema = feature.Schema("cities", - Seq( - bind[geometry.Point]("the_geom", Projection("EPSG:4326")), - bind[String]("name"))) + val schema = Schema("cities", + // (setProjection("the_geom".binds[geometry.Point], LatLon) ~ + ("the_geom".binds[geometry.Point] ~ + "name".binds[String])) val ws = workspace.Memory() val lyr = ws.create(schema) lyr += feature.fromAttributes( diff --git a/geoscript/src/test/scala/tutorial/layers.scala b/geoscript/src/test/scala/tutorial/layers.scala index 3a6ee82..b061c9a 100644 --- a/geoscript/src/test/scala/tutorial/layers.scala +++ b/geoscript/src/test/scala/tutorial/layers.scala @@ -1,9 +1,9 @@ package tutorial object Layers extends App { - import org.geoscript._, layer._ - val lyr = Layer("foo", Seq(feature.bind[String]("text"))) - lyr += feature.fromAttributes(("text", "hello")) + import org.geoscript._, feature._, layer._ + val lyr = Layer(Schema("foo", "text".binds[String])) + lyr += fromAttributes(("text", "hello")) for (feature <- lyr) println(feature.get[String]("text")) From 6eeb949e832184f3d88be88ef3584553a17f70e9 Mon Sep 17 00:00:00 2001 From: David Winslow Date: Mon, 31 Dec 2012 11:16:52 -0500 Subject: [PATCH 52/53] Publishing settings --- project/Build.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 09eae3c..0d6b1fa 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,17 +10,23 @@ object GeoScript extends Build { version := "0.8.0", gtVersion := "8.0-RC1", scalaVersion := "2.9.1", - scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked") + scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked"), + publishTo := Some(Resolver.sftp("opengeo-publish", + Some("repo.opengeo.org"), + Some(7777), + None)) ) val common = + defaultSettings ++ + meta ++ Seq[Setting[_]]( fork := true, resolvers ++= Seq( "opengeo" at "http://repo.opengeo.org/", "osgeo" at "http://download.osgeo.org/webdav/geotools/" ) - ) ++ meta ++ defaultSettings + ) val sphinxSettings = Seq( @@ -35,7 +41,7 @@ object GeoScript extends Build { ) lazy val root = - Project("root", file(".")) aggregate(css, /*docs,*/ examples, library) + Project("root", file("."), settings = common :+ (publishArtifact := false)) aggregate(css, /*docs,*/ examples, library) lazy val css = Project("css", file("geocss"), settings = common) lazy val examples = From 1112527d6a77b7ac124b0c478999835dc50dc62c Mon Sep 17 00:00:00 2001 From: David Winslow Date: Mon, 31 Dec 2012 13:37:07 -0500 Subject: [PATCH 53/53] Update to Scala 2.9.2 and make everything compile again --- .../src/main/scala/example/AllValid.scala | 4 +-- .../src/main/scala/example/ColorRamp.scala | 9 +++-- .../src/main/scala/example/FirstProject.scala | 6 +--- .../main/scala/example/Intersections.scala | 34 ++++++++----------- examples/src/main/scala/example/Postgis.scala | 7 ++-- examples/src/main/scala/example/Shp2Shp.scala | 4 +-- geocss/build.sbt | 2 +- geoscript/build.sbt | 2 +- .../src/main/scala/feature/feature.scala | 13 +++++-- geoscript/src/main/scala/filter/Filter.scala | 3 ++ .../src/main/scala/geometry/io/package.scala | 4 +-- geoscript/src/main/sphinx/download.rst | 18 ---------- project/Build.scala | 4 +-- 13 files changed, 45 insertions(+), 65 deletions(-) diff --git a/examples/src/main/scala/example/AllValid.scala b/examples/src/main/scala/example/AllValid.scala index 55fb241..15285c2 100644 --- a/examples/src/main/scala/example/AllValid.scala +++ b/examples/src/main/scala/example/AllValid.scala @@ -5,9 +5,7 @@ import org.geoscript._ object AllValid extends App { val shp = layer.Shapefile(args.head) - val invalid = shp.withAll { fs => - fs.filter { f => !f.geometry.isValid }.toIndexedSeq - } + val invalid = shp.filterNot(f => f.geometry.isValid).toIndexedSeq println("Found %s invalid features.".format(invalid.size)) for (f <- invalid) println(f.id) diff --git a/examples/src/main/scala/example/ColorRamp.scala b/examples/src/main/scala/example/ColorRamp.scala index 705eff2..f8b1c3c 100644 --- a/examples/src/main/scala/example/ColorRamp.scala +++ b/examples/src/main/scala/example/ColorRamp.scala @@ -24,8 +24,8 @@ object ColorRamp extends org.geoscript.feature.GeoCrunch { def colorRamp(data: layer.Layer, propertyName: String): style.Style = { val extract = (f: feature.Feature) => f.get[Double](propertyName) - val min = data.withAll { fs => (fs map extract).min } - val max = data.withAll { fs => (fs map extract).max } + val min = (data map extract).min + val max = (data map extract).max val k = 10 val breaks = for (i <- (0 to k)) yield (i * max + (k - i) * min) / k @@ -34,9 +34,8 @@ object ColorRamp extends org.geoscript.feature.GeoCrunch { val rules = for { (Seq(min, max), color) <- ranges zip colors - filter = "%s BETWEEN %f AND %f".format(propertyName, min, max) - } yield - Fill(hex(color)) where cql(filter) + inRange = cql("%s BETWEEN %f AND %f".format(propertyName, min, max)) + } yield Fill(hex(color)) where inRange (rules reduce (_ and _)).underlying } } diff --git a/examples/src/main/scala/example/FirstProject.scala b/examples/src/main/scala/example/FirstProject.scala index 5cd378b..8ee1d87 100644 --- a/examples/src/main/scala/example/FirstProject.scala +++ b/examples/src/main/scala/example/FirstProject.scala @@ -4,9 +4,5 @@ import org.geoscript._ object FirstProject extends App { val shp = layer.Shapefile(args(0)) - val length = shp.withAll { features => - features.map(_.geometry.length).sum - } - - println("Total Length %f".format(length)); + println("Total Length %f".format(shp.size)); } diff --git a/examples/src/main/scala/example/Intersections.scala b/examples/src/main/scala/example/Intersections.scala index 7e3b6ca..8d96f3f 100644 --- a/examples/src/main/scala/example/Intersections.scala +++ b/examples/src/main/scala/example/Intersections.scala @@ -1,40 +1,34 @@ package org.geoscript.example import org.geoscript._ +import org.geoscript.feature.{ Field, GeoField } + object Intersections { def process(src: layer.Layer, dest: layer.Layer, joinField: String) { println("Processing %s".format(src.schema.name)) - src.withAll { features => - for (feat <- features) { - src.withFiltered(filter.Filter.intersects(feat.geometry)) { - intersections => - - dest ++= - intersections.filter(_.id > feat.id).map { corner => - feature.fromAttributes( - "geom" -> (feat.geometry intersection corner.geometry), - (joinField + "Left") -> feat.get[Any](joinField), - (joinField + "Right") -> corner.get[Any](joinField) - ) - } - } - } + for (feat <- src) { + val intersecting = src.filtered(filter.intersects(feat.geometry)) + val intersections = + for (corner <- intersecting if corner.id > feat.id) yield + feature.fromAttributes( + "geom" -> (feat.geometry intersection corner.geometry), + (joinField + "Left") -> feat.get[Any](joinField), + (joinField + "Right") -> feat.get[Any](joinField)) } println("Found %d intersections".format(dest.count)) } - import feature.{ Field, Schema, bind } + import feature.{ Field, Schema } def rewrite(schema: Schema, fieldName: String): Schema = Schema( schema.name + "_intersections", Seq( - bind[geometry.Geometry]("geom", - schema.geometry.getCoordinateReferenceSystem), - bind[String](fieldName + "Left"), - bind[String](fieldName + "Right"))) + GeoField("geom", schema.geometry.crs, classOf[geometry.Geometry]), // TODO: Just reuse the field instead of building a new one? + Field(fieldName + "Left", classOf[String]), + Field(fieldName + "Right", classOf[String]))) def main(args: Array[String]) = { if (args.length == 0) { diff --git a/examples/src/main/scala/example/Postgis.scala b/examples/src/main/scala/example/Postgis.scala index 4722803..c48206f 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -2,21 +2,22 @@ package org.geoscript.example import com.vividsolutions.jts.geom.Geometry import org.geoscript._ -import feature.{ Feature, Field, Schema, bind } +import feature.{ Feature, Field, GeoField, Schema } import projection.Projection object PostgisTest extends App { val params = workspace.Params.postgis("conflict") workspace.withWorkspace(params) { conflict => - val fields = conflict.layerNamed("conflictsite").schema.fields + val fields = conflict("conflictsite").schema.fields for (field <- fields) println(field.name) workspace.withWorkspace(workspace.Params.postgis("test")) { wsTest => val test = wsTest.create(Schema( "test", - Seq(bind[String]("name"), bind[Geometry]("geom", Projection("EPSG:4326"))))) + Seq(Field("name", classOf[String]), + GeoField("geom", Projection("EPSG:4326"), classOf[Geometry])))) test += feature.fromAttributes( "name" -> "test", diff --git a/examples/src/main/scala/example/Shp2Shp.scala b/examples/src/main/scala/example/Shp2Shp.scala index c1f272a..cb6c747 100644 --- a/examples/src/main/scala/example/Shp2Shp.scala +++ b/examples/src/main/scala/example/Shp2Shp.scala @@ -14,7 +14,5 @@ object Shp2Shp extends App { } ) val dest = source.workspace.create(destSchema) - source.withAll { fs => - dest ++= fs.toIterable - } + dest ++= source } diff --git a/geocss/build.sbt b/geocss/build.sbt index 81597ec..71d78ed 100644 --- a/geocss/build.sbt +++ b/geocss/build.sbt @@ -8,7 +8,7 @@ libraryDependencies <++= gtVersion { v => } libraryDependencies ++= Seq( - "org.scala-tools.testing" %% "scalacheck" % "1.9" % "test", + "org.scala-tools.testing" % "scalacheck_2.9.1" % "1.9" % "test", "org.scalatest" %% "scalatest" % "1.8" % "test") initialCommands += """ diff --git a/geoscript/build.sbt b/geoscript/build.sbt index 8b36f3e..987aaca 100644 --- a/geoscript/build.sbt +++ b/geoscript/build.sbt @@ -16,6 +16,6 @@ libraryDependencies <++= gtVersion { v => libraryDependencies ++= Seq( "javax.media" % "jai_core" % "1.1.3", - "org.scala-tools.testing" %% "specs" % "[1.6,1.7)" % "test", + "org.scala-tools.testing" % "specs_2.9.1" % "[1.6,1.7)" % "test", "org.scalatest" %% "scalatest" % "1.8" % "test", "com.lowagie" % "itext" % "2.1.5") diff --git a/geoscript/src/main/scala/feature/feature.scala b/geoscript/src/main/scala/feature/feature.scala index dc9d009..b1ccd0e 100644 --- a/geoscript/src/main/scala/feature/feature.scala +++ b/geoscript/src/main/scala/feature/feature.scala @@ -11,13 +11,21 @@ package object feature extends org.geoscript.feature.LowPriorityImplicits { type GeoField = org.opengis.feature.`type`.GeometryDescriptor type Schema = org.opengis.feature.simple.SimpleFeatureType - def field(name: String, binding: Class[_]): Field = { + def Field(name: String, binding: Class[_]): Field = { val builder = new org.geotools.feature.AttributeTypeBuilder builder.setName(name) builder.setBinding(binding) builder.buildDescriptor(name, builder.buildType()) } + def GeoField(name: String, proj: projection.Projection, binding: Class[_]): GeoField = { + val builder = new org.geotools.feature.AttributeTypeBuilder + builder.setName(name) + builder.setBinding(binding) + builder.setCRS(proj) + builder.buildDescriptor(name, builder.buildGeometryType) + } + def setSchemaName(name: String, schema: Schema): Schema = { val builder = new org.geotools.feature.simple.SimpleFeatureTypeBuilder builder.init(schema) @@ -26,7 +34,7 @@ package object feature extends org.geoscript.feature.LowPriorityImplicits { } def fromAttributes(attributes: (String, Any)*): Feature = { - val fields = attributes.map { case (n, v) => field(n, v.getClass) } + val fields = attributes.map { case (n, v) => Field(n, v.getClass) } val schema = Schema("internal", fields) val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) for ((key, value) <- attributes) builder.set(key, value) @@ -214,6 +222,7 @@ package feature { } class RichGeoField(field: GeoField) { + def crs: projection.Projection = field.getType.getCoordinateReferenceSystem def withProjection(proj: projection.Projection): GeoField = { val builder = new org.geotools.feature.AttributeTypeBuilder builder.init(field) diff --git a/geoscript/src/main/scala/filter/Filter.scala b/geoscript/src/main/scala/filter/Filter.scala index abcd08a..0a010ee 100644 --- a/geoscript/src/main/scala/filter/Filter.scala +++ b/geoscript/src/main/scala/filter/Filter.scala @@ -17,6 +17,9 @@ package object filter { def cqlExpression(text: String) // : Expression = = gt.filter.text.ecql.ECQL.toExpression(text) + + def intersects(geom: Geometry): Filter = + factory.intersects(null, factory.literal(geom)) } // trait Filter { diff --git a/geoscript/src/main/scala/geometry/io/package.scala b/geoscript/src/main/scala/geometry/io/package.scala index e1cbec2..3fa4855 100644 --- a/geoscript/src/main/scala/geometry/io/package.scala +++ b/geoscript/src/main/scala/geometry/io/package.scala @@ -49,7 +49,7 @@ object GML extends Codec[Geometry] { def decodeFrom(in: java.io.InputStream): Geometry = { val configuration = new gml2.GMLConfiguration - val parser = new Parser(configuration, in) - parser.parse().asInstanceOf[Geometry] + val parser = new Parser(configuration) + parser.parse(in).asInstanceOf[Geometry] } } diff --git a/geoscript/src/main/sphinx/download.rst b/geoscript/src/main/sphinx/download.rst index f3c8381..6b5899f 100644 --- a/geoscript/src/main/sphinx/download.rst +++ b/geoscript/src/main/sphinx/download.rst @@ -7,24 +7,6 @@ This guide will help you get up and running with GeoScript Scala. Unlike some other GeoScript variants, the Scala variant does not ship a pre-packaged bundle with all dependencies in one download. Instead, we recommend using one of the build tools listed below for trying out GeoScript Scala; both provide an interactive interpreter along with the ability to compile and run sources. -Project Template -================ - -A project template is available for download from Github. -If you are a Git user, you can use the following command to clone the template locally:: - - $ git clone git://github.com/dwins/geoscript-project-template.git - -Alternatively, visit https://github.com/dwins/geoscript-project-template/ to download the template project in ZIP or TAR.GZ format. - -This template includes SBT, which will download GeoScript and its dependencies the first time it is run. -After extracting the archive, use a terminal to change directories into the template project and use:: - - ./sbt console - -to fetch GeoScript Scala and launch a Scala console. -Read on for more information about using SBT. - SBT === diff --git a/project/Build.scala b/project/Build.scala index 0d6b1fa..7ff0bec 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -8,8 +8,8 @@ object GeoScript extends Build { Seq[Setting[_]]( organization := "org.geoscript", version := "0.8.0", - gtVersion := "8.0-RC1", - scalaVersion := "2.9.1", + gtVersion := "8.5", + scalaVersion := "2.9.2", scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked"), publishTo := Some(Resolver.sftp("opengeo-publish", Some("repo.opengeo.org"),