diff --git a/examples/src/main/scala/example/AllValid.scala b/examples/src/main/scala/example/AllValid.scala index 01d0464..15285c2 100644 --- a/examples/src/main/scala/example/AllValid.scala +++ b/examples/src/main/scala/example/AllValid.scala @@ -5,7 +5,7 @@ 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.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 93f5d67..f8b1c3c 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 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) - rules reduce (_ and _) + 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 5b37b28..8ee1d87 100644 --- a/examples/src/main/scala/example/FirstProject.scala +++ b/examples/src/main/scala/example/FirstProject.scala @@ -4,7 +4,5 @@ import org.geoscript._ object FirstProject extends App { val shp = layer.Shapefile(args(0)) - val length = shp.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 3b11d26..8d96f3f 100644 --- a/examples/src/main/scala/example/Intersections.scala +++ b/examples/src/main/scala/example/Intersections.scala @@ -1,38 +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)) - for (feat <- src.features) { - val intersections = - src.filter(filter.Filter.intersects(feat.geometry)) - dest ++= - intersections.filter(_.id > feat.id).map { corner => - feature.Feature( + 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") -> corner.get[Any](joinField) - ) - } + (joinField + "Right") -> feat.get[Any](joinField)) } println("Found %d intersections".format(dest.count)) } - def rewrite(schema: feature.Schema, fieldName: String): feature.Schema = - feature.Schema( + import feature.{ Field, Schema } + 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( + 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) { @@ -40,7 +36,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 c8e2628..c48206f 100644 --- a/examples/src/main/scala/example/Postgis.scala +++ b/examples/src/main/scala/example/Postgis.scala @@ -2,22 +2,27 @@ package org.geoscript.example import com.vividsolutions.jts.geom.Geometry import org.geoscript._ -import feature.{ Feature, Field } +import feature.{ Feature, Field, GeoField, Schema } +import projection.Projection 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("conflictsite").schema.fields + + for (field <- fields) println(field.name) + + workspace.withWorkspace(workspace.Params.postgis("test")) { wsTest => + val test = wsTest.create(Schema( + "test", + Seq(Field("name", classOf[String]), + GeoField("geom", Projection("EPSG:4326"), classOf[Geometry])))) + + test += feature.fromAttributes( + "name" -> "test", + "geom" -> geometry.point(43,74) + ) + } + } } diff --git a/examples/src/main/scala/example/Render.scala b/examples/src/main/scala/example/Render.scala index 4d2ae1f..3249287 100644 --- a/examples/src/main/scala/example/Render.scala +++ b/examples/src/main/scala/example/Render.scala @@ -1,22 +1,38 @@ 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._, render._ -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 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(Content(states, theme), Stretch(bounds), 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(Content(states, theme), Stretch(bounds), win) + } catch { + case _ => () + } + } + updated = lastModified + } } + watcher.start() } diff --git a/examples/src/main/scala/example/Shp2Shp.scala b/examples/src/main/scala/example/Shp2Shp.scala index b3b8357..cb6c747 100644 --- a/examples/src/main/scala/example/Shp2Shp.scala +++ b/examples/src/main/scala/example/Shp2Shp.scala @@ -2,18 +2,17 @@ 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.copy(projection = proj) + case (g: GeoField) => g.withProjection(Projection(proj)) case (f: Field) => f } ) val dest = source.workspace.create(destSchema) - dest ++= source.features map { f => - f.update(destSchema.geometry.name -> (f.geometry /* in proj */)) - } + 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/geocss/src/main/java/org/geoscript/geocss/compat/CSS2SLD.java b/geocss/src/main/java/org/geoscript/geocss/compat/CSS2SLD.java index 46ff93c..0624254 100644 --- a/geocss/src/main/java/org/geoscript/geocss/compat/CSS2SLD.java +++ b/geocss/src/main/java/org/geoscript/geocss/compat/CSS2SLD.java @@ -5,16 +5,7 @@ import org.geotools.styling.Style; public final class CSS2SLD { - static abstract class Converter { - public abstract Style convert(Reader input); - public abstract Style convert(Reader input, URL baseURL); - } - - protected static Converter impl; - - static { - impl = new Impl(); - } + protected static final Converter impl = new Converter(); private CSS2SLD() throws Exception { throw new Exception("You shouldn't be instantiating this class"); diff --git a/geocss/src/main/scala/org/geoscript/geocss/compat/Impl.scala b/geocss/src/main/scala/org/geoscript/geocss/compat/Impl.scala index c357b8d..6e9edef 100644 --- a/geocss/src/main/scala/org/geoscript/geocss/compat/Impl.scala +++ b/geocss/src/main/scala/org/geoscript/geocss/compat/Impl.scala @@ -4,14 +4,14 @@ import org.geotools.styling.Style import sys.error package compat { - class Impl extends CSS2SLD.Converter { - override def convert(input: java.io.Reader): Style = + class Converter protected[compat]() { + def convert(input: java.io.Reader): Style = CssParser.parse(input) match { case CssParser.Success(style, _) => (new Translator).css2sld(style) case (ns: CssParser.NoSuccess) => error(ns.toString) } - override def convert(input: java.io.Reader, baseURL: java.net.URL): Style = + def convert(input: java.io.Reader, baseURL: java.net.URL): Style = CssParser.parse(input) match { case CssParser.Success(style, _) => (new Translator(Some(baseURL))).css2sld(style) case (ns: CssParser.NoSuccess) => error(ns.toString) diff --git a/geoscript/build.sbt b/geoscript/build.sbt index d292210..987aaca 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, @@ -12,15 +8,14 @@ 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 ) } -libraryDependencies ++= - Seq( - "javax.media" % "jai_core" % "1.1.3", - "org.scala-tools.testing" %% "specs" % "[1.6,1.7)" % "test", - "org.scalatest" %% "scalatest" % "1.8" % "test", - "com.lowagie" % "itext" % "2.1.5" - ) +libraryDependencies ++= Seq( + "javax.media" % "jai_core" % "1.1.3", + "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/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. - diff --git a/geoscript/src/main/scala/Converters.scala b/geoscript/src/main/scala/Converters.scala index 552a15b..4fd3feb 100644 --- a/geoscript/src/main/scala/Converters.scala +++ b/geoscript/src/main/scala/Converters.scala @@ -1,32 +1,47 @@ -package org { - package object geoscript { - import geometry._ - - implicit def enrichGeometry(geometry: Geometry): RichGeometry = - new RichGeometry(geometry) - - implicit def enrichEnvelope(envelope: Envelope): RichEnvelope = - new RichEnvelope(envelope) - - implicit def enrichPoint(point: Point): RichPoint = - new RichPoint(point) - - 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) - ) - } +package org +package object geoscript { + import feature._ + import geometry._ + import layer._ + import workspace._ + import render._ + import projection.{ Projection, RichProjection, Projectable, Referenced } + + implicit def enrichGeometry(g: Geometry): RichGeometry = + new RichGeometry(g) + + implicit def enrichPoint(p: Point): RichPoint = new RichPoint(p) + + 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) + + implicit def enrichGeoField(f: GeoField) = new RichGeoField(f) + + implicit def enrichSchema(s: Schema) = new RichSchema(s) + + 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) + + 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/feature/Feature.scala b/geoscript/src/main/scala/feature/Feature.scala deleted file mode 100644 index df3fa9b..0000000 --- a/geoscript/src/main/scala/feature/Feature.scala +++ /dev/null @@ -1,311 +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: _*) - } - - 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 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 - } - } -} - -/** - * 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) - } - - 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 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 properties: Map[String, Any] = Map(props.toSeq: _*) - } - } -} - -/** - * 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/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 new file mode 100644 index 0000000..b1ccd0e --- /dev/null +++ b/geoscript/src/main/scala/feature/feature.scala @@ -0,0 +1,257 @@ +package org.geoscript + +import geometry._ +import org.opengis.feature.`type`.AttributeDescriptor +import scala.collection.JavaConverters._ + +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 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) + 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) + val builder = new org.geotools.feature.simple.SimpleFeatureBuilder(schema) + for ((key, value) <- attributes) builder.set(key, value) + builder.buildFeature(null) + } + + def named[A : Bindable](name: String): AbsoluteFieldSet[A] = + new NamedFieldSet(name) + + implicit def bindingSugarForString(name: String) = + new { + def binds[T : Bindable] = named[T](name) + } + + + 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.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] + } + + sealed trait AbsoluteFieldSet[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 ~[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] + + def build(builder: org.geotools.feature.simple.SimpleFeatureBuilder, t: T): Unit = + builder.set(name, t) + + def toSeq: Seq[Field] = + Seq(implicitly[Bindable[T]].bind(name)) + } + + 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: 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) + 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 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) + def get(index: Int): Field = schema.getDescriptor(index) + def withName(name: String): Schema = Schema(name, fields) + 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 { + 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 attributes: Seq[Any] = + feature.getAttributes.asScala.toSeq + 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) { + def name: String = field.getName.getLocalPart + def binding: Class[_] = field.getType.getBinding + } + + 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) + builder.setCRS(proj) + 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..0a010ee 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,54 @@ 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 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 cql(text: String): Filter = + gt.filter.text.ecql.ECQL.toFilter(text) + + 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 { +// 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/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..995890b 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) { /** @@ -35,6 +9,16 @@ 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. */ @@ -49,8 +33,10 @@ 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 + + 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 @@ -58,14 +44,28 @@ 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 } + +class RichEnvelope(e: Envelope) { + def maxY = e.getMaxY + 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) { + def x = point.getX + def y = point.getY +} 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/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 -// } 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/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 -// } 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/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 -// } 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/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 -// } 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/io/package.scala b/geoscript/src/main/scala/geometry/io/package.scala index 532b2e3..3fa4855 100644 --- a/geoscript/src/main/scala/geometry/io/package.scala +++ b/geoscript/src/main/scala/geometry/io/package.scala @@ -1,74 +1,55 @@ 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._ +import org.geotools.geojson.geom.GeometryJSON 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 readFrom(in: java.io.Reader): Geometry = reader.read(in) - 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 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 decodeFrom(in: java.io.InputStream): Geometry = + reader.read(new com.vividsolutions.jts.io.InputStreamInStream(in)) - 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 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 Codec[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) + } + + def decodeFrom(in: java.io.InputStream): Geometry = { + val configuration = new gml2.GMLConfiguration + val parser = new Parser(configuration) + parser.parse(in).asInstanceOf[Geometry] } } diff --git a/geoscript/src/main/scala/geometry/package.scala b/geoscript/src/main/scala/geometry/package.scala index 3746efe..90dc671 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 @@ -13,6 +14,151 @@ package object geometry { type Coordinate = jts.Coordinate type Envelope = jts.Envelope + type Transform = jts.util.AffineTransformation + + 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 + 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 envelope(minX: Double, maxX: Double, minY: Double, maxY: Double): Envelope = + new jts.Envelope(minX, maxX, minY, maxY) + + def point(x: Double, y: Double): Point = + 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) + } + + def simplify(g: Geometry, tolerance: Double): 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 + } + + 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/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/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..548348b --- /dev/null +++ b/geoscript/src/main/scala/layer/package.scala @@ -0,0 +1,95 @@ +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(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 = { + val schemata = (fs map (_.getFeatureType)).toSet + + // 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) 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 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) + } + } + + 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: Traversable[feature.Feature]) { + val tx = new org.geotools.data.DefaultTransaction + layer.setTransaction(tx) + try { + val featureColl = org.geotools.feature.FeatureCollections.newCollection() + 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 { + case ex => tx.rollback(); throw ex + } finally { + tx.close() + layer.setTransaction(org.geotools.data.Transaction.AUTO_COMMIT) + } + } + } + + 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(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..726e6b7 --- /dev/null +++ b/geoscript/src/main/scala/projection/HasProjection.scala @@ -0,0 +1,58 @@ +package org.geoscript +package projection + +trait HasProjection[G] { + def setProjection(p: Projection)(g: G): G + def transform(p: Projection)(g: G): G +} + +object HasProjection { + import feature._ + + 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() {} + def hasNext = it.hasNext + def next = it.next + } + + new org.geotools.data.crs.ReprojectFeatureIterator( + dummyIterator, + SchemataHaveProjections.setProjection(p)(g.schema), + lookupTransform(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] + } + + def transform(p: Projection)(g: GeoField): GeoField = setProjection(p)(g) + } + + 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(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 new file mode 100644 index 0000000..ef2d4f5 --- /dev/null +++ b/geoscript/src/main/scala/projection/Projectable.scala @@ -0,0 +1,34 @@ +package org.geoscript +package projection + +import feature._, geometry._ +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, lookupTransform(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, lookupTransform(from, to)) + } +} diff --git a/geoscript/src/main/scala/projection/Projection.scala b/geoscript/src/main/scala/projection/Projection.scala index 4981b0b..a367d11 100644 --- a/geoscript/src/main/scala/projection/Projection.scala +++ b/geoscript/src/main/scala/projection/Projection.scala @@ -1,90 +1,78 @@ -package org.geoscript.projection +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 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 + 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") } - /** - * Get the official spatial reference identifier for this projection, if any - */ - def id: String = CRS.toSRS(crs) - - /** - * Get the Well Known Text specification of this projection. - * - * @see http://en.wikipedia.org/wiki/Well-known_text - */ - def wkt: String = crs.toString() - - override def toString: String = - id match { - case null => "" - case id => id + 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) } + + // NOTE: Some data types store projections but not coordinates (eg, Schema). + // For these, force and reproject are the same. + + // Update projection tracking information without modifying coordinates + def force[G : HasProjection](proj: Projection, g: G): G = + implicitly[HasProjection[G]].setProjection(proj)(g) + + // 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 } -/** - * The Projection object provides several methods for getting Projection - * instances. - */ -object Projection { - val forceXY = System.getProperty("org.geotools.referencing.forceXY") +package projection { + import org.geoscript.serialize._ - if (forceXY == null || forceXY.toBoolean == false) - System.setProperty("org.geotools.referencing.forceXY", "true") + class RichProjection(p: Projection) { + def id: Option[String] = Option(CRS.lookupIdentifier(p, 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) + 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) } - ) - - /** - * Create a Projection by wrapping a GeoTools CoordinateReferenceSystem - * object. - */ - def apply(crs: CoordinateReferenceSystem) = new Projection(crs) + CRS.parseWKT(accum.toString) + } - implicit def codeToCRS(code: String) = Projection(code) - implicit def wrapCRS(crs: CoordinateReferenceSystem) = Projection(crs) - implicit def unwrapCRS(proj: Projection) = proj.crs + def writeTo(out: java.io.Writer, p: Projection): Unit = { + out.write(p.toWKT) + } + } } diff --git a/geoscript/src/main/scala/projection/Referenced.scala b/geoscript/src/main/scala/projection/Referenced.scala new file mode 100644 index 0000000..0e82ac4 --- /dev/null +++ b/geoscript/src/main/scala/projection/Referenced.scala @@ -0,0 +1,73 @@ +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 project(p: Projection): T + def forceNative: T = project(native.get) +} + +object Referenced { + def apply[T : Projectable](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 project(p: Projection): T = value + def native = None + } + + private class Physical[T : Projectable](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 project(p: Projection): T = + 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] { + 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 project(p: Projection): U = f(base.project(p)) + + def native = base.native + } + + 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 project(p: Projection): U = + f(base.project(p)).project(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/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..5bb99cf --- /dev/null +++ b/geoscript/src/main/scala/render/package.scala @@ -0,0 +1,295 @@ +package org.geoscript + +import style.combinators._, feature._, geometry._, projection._, serialize._ + +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): Layer + def defaultStyle(t: T): Layer + } + + object Stylable { + private val defaultSchema = + Schema("empty", "the_geom".binds[Geometry]) + + 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] = + new Stylable[layer.Layer] { + def applyStyle(l: layer.Layer, s: style.Style): Layer = + new gt.map.FeatureLayer(l, s, 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(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 => + def render(draw: Draw): T + + def map[U](f: T => U): Canvas[U] = new Canvas[U] { + def render(draw: Draw): U = + f(self.render(draw)) + } + } + + 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, + 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, size) + graphics.dispose() + img + } + } + + class Window extends Canvas[javax.swing.JFrame] { + private val component = new javax.swing.JComponent { + // createBufferStrategy(2) + setBackground(java.awt.Color.WHITE) + override def paintComponent(g: awt.Graphics) = + draw(g.asInstanceOf[awt.Graphics2D], dimensionAsTuple(getSize())) + } + private val frame = new javax.swing.JFrame + // frame.setResizable(false) + frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE) + frame.add(component) + frame.setSize(256, 256) + + private var draw: Draw = { (_, _) => } + + 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): Content = + empty.withData(t) + def apply[T : Stylable](t: T, s: style.Style): Content = + empty.withData(t, s) + } + + 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) + newContent.addLayer(l) + newContent + } + 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)) + } +} + +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 + + 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, + 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), refEnv) + } + } + + // 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 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) + + def png[Spec, Out] + (dest: Spec = (), size: (Int, Int) = (512, 512), background: Color = Transparent) + (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)) + + private def renderHints(kv: (RenderingHints.Key, Any)*) = + 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/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..bd92276 --- /dev/null +++ b/geoscript/src/main/scala/serialize/Encodable.scala @@ -0,0 +1,34 @@ +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 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 + try { + op(buff) + buff.toByteArray + } finally + buff.close() + } + } +} diff --git a/geoscript/src/main/scala/serialize/Format.scala b/geoscript/src/main/scala/serialize/Format.scala new file mode 100644 index 0000000..fffe603 --- /dev/null +++ b/geoscript/src/main/scala/serialize/Format.scala @@ -0,0 +1,18 @@ +package org.geoscript.serialize + +trait Reader[+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 format(t: T): String = write(t, ())(Writable.writeString) + + def write[Spec, Out](t: T, spec: Spec)(implicit writable: Writable[Spec, Out]): Out = + implicitly[Writable[Spec, Out]].write(spec)(writeTo(_, t)) + + def writeTo(sink: java.io.Writer, t: T): Unit +} + +trait Format[T] extends Reader[T] with Writer[T] 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/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..a86eefd 100644 --- a/geoscript/src/main/scala/style/Style.scala +++ b/geoscript/src/main/scala/style/Style.scala @@ -1,10 +1,10 @@ -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 extends style.Style { +sealed abstract trait Style { def where(filter: Filter): Style def aboveScale(s: Double): Style def belowScale(s: Double): Style @@ -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() @@ -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 diff --git a/geoscript/src/main/scala/viewer/Viewer.scala b/geoscript/src/main/scala/viewer/Viewer.scala index 9cff648..bf3074b 100644 --- a/geoscript/src/main/scala/viewer/Viewer.scala +++ b/geoscript/src/main/scala/viewer/Viewer.scala @@ -1,56 +1,21 @@ 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 +package object viewer { +} - 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 viewer { + trait Viewable[V] { + def bounds(v: V): geometry.Envelope + def draw(v: V, canvas: java.awt.Graphics2D): Unit } -} -/** - * The Viewer object provides some rudimentary methods for rendering - * geospatial information on-screen. - */ -object Viewer { - private var window: Option[(swing.Window, MapWidget)] = None + object Viewable { + implicit object geometryIsViewable extends Viewable[geometry.Geometry] { + def bounds(g: geometry.Geometry): geometry.Envelope = + g.getEnvelopeInternal() - 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 draw(g: geometry.Geometry, canvas: java.awt.Graphics2D) { + } } } } diff --git a/geoscript/src/main/scala/workspace/Workspace.scala b/geoscript/src/main/scala/workspace/Workspace.scala index 6a2ee60..b4ad94f 100644 --- a/geoscript/src/main/scala/workspace/Workspace.scala +++ b/geoscript/src/main/scala/workspace/Workspace.scala @@ -6,103 +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 = new Layer { - val name = theName - val workspace = Workspace.this - val store = underlying - } - - 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")) +// } +// } +// } diff --git a/geoscript/src/main/scala/workspace/package.scala b/geoscript/src/main/scala/workspace/package.scala new file mode 100644 index 0000000..25f9df6 --- /dev/null +++ b/geoscript/src/main/scala/workspace/package.scala @@ -0,0 +1,172 @@ +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, + user: String = null, + host: String = null, + password: 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), + 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), + 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 = "localhost", + user: String = "postgres", + port: Int = 5432, + 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), + USER.key -> Option(user), + HOST.key -> Option(host), + 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), + 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 apply(name: String): Layer = (ws getFeatureSource name).asInstanceOf[Layer] + def count = ws.getTypeNames.length + def create(schema: feature.Schema): Layer = { + ws.createSchema(schema) + (ws getFeatureSource schema.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) { + 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() + } + } + + object Memory { + def apply(): Workspace = new org.geotools.data.memory.MemoryDataStore() + } +} diff --git a/geoscript/src/main/sphinx/download.rst b/geoscript/src/main/sphinx/download.rst index 5ec931a..6b5899f 100644 --- a/geoscript/src/main/sphinx/download.rst +++ b/geoscript/src/main/sphinx/download.rst @@ -1,67 +1,92 @@ -.. 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: +SBT +=== -#. Build GeoScript.scala from sources -#. Use GeoScript in a Scala interpreter +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. - -Install sbt +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..e712c70 100644 --- a/geoscript/src/main/sphinx/index.rst +++ b/geoscript/src/main/sphinx/index.rst @@ -1,19 +1,20 @@ -.. 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 =============== .. 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..1865ad6 100644 --- a/geoscript/src/main/sphinx/quickstart.rst +++ b/geoscript/src/main/sphinx/quickstart.rst @@ -1,57 +1,104 @@ -.. 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_sbt_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 + + scala> import org.geoscript._ + import org.geoscript._ + + scala> geometry.point(1,2) + res0: org.geoscript.geometry.package.Point = POINT (1 2) + + 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 + +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:: -Adding to the PATH ------------------- + 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. -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:: +Now, instead of using SBT's ``console`` command, use ``run`` to run the code:: - $ echo 'export PATH=/path/to/geoscript/:$PATH' >> ~/.profile - $ . ~/.profile # apply changes, otherwise they won't apply until next login + $ 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 -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" diff --git a/geoscript/src/test/scala/GeoHashTest.scala b/geoscript/src/test/scala/GeoHashTest.scala index 7004b63..8d14926 100644 --- a/geoscript/src/test/scala/GeoHashTest.scala +++ b/geoscript/src/test/scala/GeoHashTest.scala @@ -1,7 +1,7 @@ package org.geoscript import org.scalatest._, matchers._ -import GeoHash._ +import tutorial.GeoHash._ class GeoHashTest extends FunSuite with ShouldMatchers { val cases = Seq( diff --git a/geoscript/src/test/scala/UsageTests.scala b/geoscript/src/test/scala/UsageTests.scala index aed3905..c89ab92 100644 --- a/geoscript/src/test/scala/UsageTests.scala +++ b/geoscript/src/test/scala/UsageTests.scala @@ -2,13 +2,14 @@ package org.geoscript import org.scalatest._, matchers._ -import geometry._ -import projection._ +import geometry._, projection._, feature._ class UsageTests extends FunSuite with ShouldMatchers { + System.setProperty("org.geotools.referencing.forceXY", "true") + test("work like on the geoscript homepage") { - var p = Point(-111, 45.7) - var p2 = (Projection("epsg:4326") to Projection("epsg:26912"))(p) + var p = point(-111, 45.7) + var p2 = transform(Projection("epsg:4326"), Projection("epsg:26912"), p) var poly = p.buffer(100) p2.x should be(closeTo(499999.0, 1)) @@ -17,22 +18,22 @@ class UsageTests extends FunSuite with ShouldMatchers { } test("linestrings should be easy") { - LineString( + lineString(Seq( (10.0, 10.0), (20.0, 20.0), (30.0, 40.0) - ).length should be(closeTo(36.503, 0.001)) + )).length should be(closeTo(36.503, 0.001)) - LineString((10, 10), (20.0, 20.0), (30, 40)) + lineString(Seq((10, 10), (20.0, 20.0), (30, 40))) .length should be(closeTo(36.503, 0.001)) } - test("polygon should be easy") { - Polygon( - LineString((10, 10), (10, 20), (20, 20), (20, 15), (10, 10)) - ).area should be (75) + test("polygon should be easy") { + polygon( + Seq((10, 10), (10, 20), (20, 20), (20, 15), (10, 10)) + ).area should be(75) } test("multi point should be easy") { - MultiPoint((20, 20), (10.0, 10.0)).area should be (0) + multiPoint(Seq((20, 20), (10.0, 10.0))).area should be(0) } val states = getClass().getResource("/data/states.shp").toURI @@ -53,7 +54,7 @@ class UsageTests extends FunSuite with ShouldMatchers { test("support search") { val shp = layer.Shapefile(statesPath) - shp.features.find(_.id == "states.1") should be ('defined) + shp.find(_.id == "states.1") should be ('defined) } test("provide access to schema information") { @@ -61,7 +62,7 @@ class UsageTests extends FunSuite with ShouldMatchers { shp.schema.name should be ("states") val field = shp.schema.get("STATE_NAME") field.name should be ("STATE_NAME") - (field.gtBinding: AnyRef) should be (classOf[java.lang.String]) + (field.binding: AnyRef) should be (classOf[java.lang.String]) } test("provide access to the containing workspace") { @@ -77,27 +78,23 @@ class UsageTests extends FunSuite with ShouldMatchers { test("allow creating new layers") { val mem = workspace.Memory() mem.names should be ('empty) - var dummy = mem.create("dummy", - feature.Field("name", classOf[String]), - feature.Field("geom", classOf[com.vividsolutions.jts.geom.Geometry], "EPSG:4326") - ) + var dummy = mem.create(Schema("dummy", + "name".binds[String] ~ (force(LatLon, "geom".binds[Geometry])))) mem.names.length should be (1) - dummy += feature.Feature( + dummy += feature.fromAttributes( "name" -> "San Francisco", - "geom" -> Point(37.78, -122.42) + "geom" -> point(37.78, -122.42) ) - dummy += feature.Feature( + dummy += feature.fromAttributes( "name" -> "New York", - "geom" -> Point(40.47, -73.58) + "geom" -> point(40.47, -73.58) ) dummy.count should be (2) - dummy.features.find( - f => f.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/org/geoscript/geometry/SerializationSpec.scala b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala index 60a6521..4b65428 100644 --- a/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala +++ b/geoscript/src/test/scala/org/geoscript/geometry/SerializationSpec.scala @@ -1,83 +1,75 @@ package org.geoscript package geometry -import org.geoscript.io.{ Sink, Source } import org.scalatest._, matchers._ class SerializationSpec extends FunSuite with ShouldMatchers { test("round-trip points") { - val p = Point(100, 0) - val json = io.GeoJSON.write(p, Sink.string) + val p = point(100, 0) + val json = GeoJSON.format(p) json should be("""{"type":"Point","coordinates":[100,0.0]}""") - // io.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((100, 0), (101, 1)) - io.GeoJSON.write(ls, Sink.string) should be - ("""{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""") + val ls = lineString(Seq((100, 0), (101, 1))) + GeoJSON.format(ls) should be( + """{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""") } test("round-trip polygons") { - 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( - (100.2, 0.2), (100.8, 0.2), (100.8, 0.8), (100.2, 0.8), (100.2, 0.2) - )) + 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)) + ) ) - io.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]]]}""") - io.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((100.0, 0.0), (101.0, 1.0)) - io.GeoJSON.write(mp, Sink.string) should be( + val mp = multiPoint(Seq((100.0, 0.0), (101.0, 1.0))) + GeoJSON.format(mp) should be( """{"type":"MultiPoint","coordinates":[[100,0.0],[101,1]]}""") } test("round-trip a MultiLineString") { - 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) should be( + GeoJSON.format(mls) should be( """{"type":"MultiLineString","coordinates":[[[100,0.0],[101,1]],[[102,2],[103,3]]]}""") } test("round-trip a MultiPolygon") { - val mp = MultiPolygon( - Polygon(LineString( - (102, 2), (103, 2), (103, 3), (102, 3), (102, 2) - )), - Polygon(LineString( - (100, 0), (101, 0), (101, 1), (100, 1), (100, 0) - ), - Seq(LineString( - (100.2, 0.2), (100.8, 0.2), (100.8, 0.8), (100.2, 0.8), (100.2, 0.2) - )) - ) - ) + val mp = multiPolygon(Seq( + (Seq((102, 2), (103, 2), (103, 3), (102, 3), (102, 2)), Nil), + (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)))))) - io.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 = GeometryCollection( - Point(100.0, 0.0), - LineString((101.0, 0.0), (102.0, 1.0)) - ) - - io.GeoJSON.write(gc, Sink.string) should be( + val gc = multi(Seq(point(100, 0), lineString(Seq((101, 0), (102, 1))))) + 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/org/geoscript/workspaces/MemorySpec.scala b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala index 6e6d2bc..559e61c 100644 --- a/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala +++ b/geoscript/src/test/scala/org/geoscript/workspaces/MemorySpec.scala @@ -1,19 +1,21 @@ package org.geoscript package workspace +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", - feature.Field("the_geom", classOf[Point], "EPSG:4326"), - feature.Field("name", classOf[String]) - ) + 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.Feature( - "the_geom" -> geometry.Point(0, 0), + lyr += feature.fromAttributes( + "the_geom" -> geometry.point(0, 0), "name" -> "test" ) lyr.envelope should not be(null) diff --git a/geoscript/src/test/scala/tutorial/BasicGeometry.scala b/geoscript/src/test/scala/tutorial/BasicGeometry.scala new file mode 100644 index 0000000..b2ebef0 --- /dev/null +++ b/geoscript/src/test/scala/tutorial/BasicGeometry.scala @@ -0,0 +1,61 @@ +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) + + // serialization + geometry.WKT.format(point) + geometry.GeoJSON.format(point) + + // deserialization + geometry.WKT.read("POINT (30 10)") + geometry.GeoJSON.read("POINT (30 10)") +} 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) } /** diff --git a/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala b/geoscript/src/test/scala/tutorial/GeometryAdvanced.scala new file mode 100644 index 0000000..1a14239 --- /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) + draw(Content(poly), canvas = new Window) +} + +object GeometrySimplify extends App { + val poly = point(0, 0).buffer(1) + 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) + draw( + Content(Seq(circle, Transform.translated(dx=0.75, dy=0)(circle))), + canvas = new Window) + + val box = polygon(Seq((0, 0), (1, 0), (1, 1), (0, 1), (0, 0))) + draw( + Content(Seq( + box, Transform.sheared(x=1, y=0).scaled(x=2, y=2)(box))), + 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) + draw( + Content(Seq(cross, Transform.rotated(theta=math.toRadians(45))(cross))), + canvas = new Window) +} 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 +} diff --git a/geoscript/src/test/scala/tutorial/layers.scala b/geoscript/src/test/scala/tutorial/layers.scala new file mode 100644 index 0000000..b061c9a --- /dev/null +++ b/geoscript/src/test/scala/tutorial/layers.scala @@ -0,0 +1,10 @@ +package tutorial + +object Layers extends App { + 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")) +} diff --git a/geoscript/src/test/scala/tutorial/visualize.scala b/geoscript/src/test/scala/tutorial/visualize.scala new file mode 100644 index 0000000..a921b09 --- /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), Stretch(bounds), window) +} diff --git a/project/Build.scala b/project/Build.scala index 2517954..7ff0bec 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -7,20 +7,26 @@ object GeoScript extends Build { val meta = Seq[Setting[_]]( organization := "org.geoscript", - version := "0.7.4", - gtVersion := "8.0-RC1", - scalaVersion := "2.9.1", - scalacOptions ++= Seq("-deprecation", "-Xlint", "-unchecked") + version := "0.8.0", + gtVersion := "8.5", + scalaVersion := "2.9.2", + 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 =