Skip to content

Commit ed03aef

Browse files
committed
Add some implicit magic in the serialize package
Now client code looks like: GeoJSON.read(text) Instead of: GeoJSON.read(Source.string(text))
1 parent 79c64de commit ed03aef

File tree

4 files changed

+161
-123
lines changed

4 files changed

+161
-123
lines changed

geoscript/src/main/scala/geometry/io/package.scala

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,44 @@ object WKT extends Format[Geometry] {
66
private val reader = new com.vividsolutions.jts.io.WKTReader()
77
private val writer = new com.vividsolutions.jts.io.WKTWriter()
88

9-
def read(source: Source): Geometry =
10-
source { in =>
11-
val chars = new java.io.InputStreamReader(in)
12-
val res = reader.read(chars)
13-
chars.close()
14-
15-
res
16-
}
17-
18-
def write[T](geom: Geometry, sink: Sink[T]): T =
19-
sink { out =>
20-
val chars = new java.io.OutputStreamWriter(out)
21-
writer.write(geom, chars)
22-
chars.close()
23-
}
9+
def readFrom(in: java.io.Reader): Geometry = reader.read(in)
10+
11+
def writeTo(out: java.io.Writer, geom: Geometry) { writer.write(geom, out) }
2412
}
2513

26-
object WKB extends Format[Geometry] {
14+
object WKB extends Codec[Geometry] {
2715
private val reader = new com.vividsolutions.jts.io.WKBReader()
2816
private val writer = new com.vividsolutions.jts.io.WKBWriter()
2917

30-
def read(source: Source): Geometry =
31-
source { in =>
32-
val s = new com.vividsolutions.jts.io.InputStreamInStream(in)
33-
reader.read(s)
34-
}
35-
36-
def write[T](geom: Geometry, sink: Sink[T]): T =
37-
sink { out =>
38-
val s = new com.vividsolutions.jts.io.OutputStreamOutStream(out)
39-
writer.write(geom, s)
40-
}
18+
def decodeFrom(in: java.io.InputStream): Geometry =
19+
reader.read(new com.vividsolutions.jts.io.InputStreamInStream(in))
20+
21+
def encodeTo(out: java.io.OutputStream, g: Geometry) {
22+
writer.write(g, new com.vividsolutions.jts.io.OutputStreamOutStream(out))
23+
}
4124
}
4225

4326
import org.geotools.geojson.geom.GeometryJSON
4427
class GeoJSON(format: GeometryJSON) extends Format[Geometry] {
4528
def this(precision: Int) = this(new GeometryJSON(precision))
46-
def read(source: Source): Geometry = source { format.read(_) }
47-
def write[T](g: Geometry, sink: Sink[T]): T = sink { format.write(g, _) }
29+
30+
def readFrom(source: java.io.Reader) = format.read(source)
31+
32+
def writeTo(sink: java.io.Writer, g: Geometry): Unit =
33+
format.write(g, sink)
4834
}
4935

5036
object GeoJSON extends GeoJSON(new GeometryJSON)
5137

52-
import org.geotools.xml.{ Parser, Encoder }
53-
import org.geotools.gml2
38+
object GML extends Encoder[Geometry] {
39+
import org.geotools.xml.{ Parser, Encoder }
40+
import org.geotools.gml2
5441

55-
object GML extends Writer[Geometry] {
56-
def write[T](g: Geometry, sink: Sink[T]): T = {
42+
def encodeTo(sink: java.io.OutputStream, g: Geometry) {
5743
val configuration = new gml2.GMLConfiguration
5844
val encoder = new Encoder(configuration)
5945
val nsUri = configuration.getNamespaceURI
6046
val qname = new javax.xml.namespace.QName(nsUri, g.getGeometryType)
61-
sink { encoder.encode(g, qname, _) }
47+
encoder.encode(g, qname, sink)
6248
}
6349
}
Lines changed: 122 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,152 @@
1-
package org.geoscript
2-
package serialize
1+
package org.geoscript.serialize
32

4-
import java.io.{ File, InputStream, OutputStream }
3+
import java.io.File
54

65
trait Reader[+T] {
7-
def read(source: Source): T
6+
def read[U : Readable](source: U): T =
7+
implicitly[Readable[U]].read(source)(readFrom(_))
8+
def readFrom(source: java.io.Reader): T
89
}
910

1011
trait Writer[-T] {
11-
def write[A](t: T, sink: Sink[A]): A
12-
}
12+
def format(t: T): String = write(t, ())(Writable.writeString)
1313

14-
trait Format[T] extends Reader[T] with Writer[T]
14+
def write[Spec, Out](t: T, spec: Spec)(implicit writable: Writable[Spec, Out]): Out =
15+
implicitly[Writable[Spec, Out]].write(spec)(writeTo(_, t))
1516

16-
trait Sink[T] {
17-
def apply(op: OutputStream => Unit): T
17+
def writeTo(sink: java.io.Writer, t: T): Unit
1818
}
1919

20-
object Sink {
21-
implicit def stream(out: OutputStream): Sink[Unit] =
22-
new Sink[Unit] {
23-
def apply(op: OutputStream => Unit) = op(out)
24-
}
20+
trait Format[T] extends Reader[T] with Writer[T]
2521

26-
implicit def file(name: String): Sink[File] =
27-
file(new java.io.File(name))
22+
trait Readable[T] {
23+
def read[U](t: T)(op: java.io.Reader => U): U
24+
}
2825

29-
implicit def file(file: File): Sink[File] =
30-
new Sink[java.io.File] {
31-
def apply(op: OutputStream => Unit) = {
32-
val output =
33-
new java.io.BufferedOutputStream(new java.io.FileOutputStream(file))
34-
stream(output)(op)
35-
output.close()
26+
trait Writable[Spec, Out] {
27+
def write(spec: Spec)(op: java.io.Writer => Unit): Out
28+
}
3629

37-
file
30+
object Writable {
31+
implicit object writeWriter extends Writable[java.io.Writer, Unit] {
32+
def write(spec: java.io.Writer)(op: java.io.Writer => Unit): Unit = op(spec)
33+
}
34+
35+
implicit object writeFile extends Writable[File, Unit] {
36+
def write(spec: File)(op: java.io.Writer => Unit): Unit = {
37+
val writer = new java.io.FileWriter(spec)
38+
try
39+
op(writer)
40+
finally
41+
writer.close()
42+
}
43+
}
44+
45+
implicit object writeString extends Writable[Unit, String] {
46+
def write(spec: Unit)(op: java.io.Writer => Unit): String = {
47+
val writer = new java.io.StringWriter
48+
try {
49+
op(writer)
50+
writer.toString
51+
} finally {
52+
writer.close()
3853
}
3954
}
55+
}
56+
}
4057

41-
def string: Sink[String] =
42-
new Sink[String] { // TODO: Needs better text support
43-
def apply(op: OutputStream => Unit) =
44-
buffer(op).view.map(_.toChar).mkString
58+
object Readable {
59+
implicit object readReader extends Readable[java.io.Reader] {
60+
def read[T](in: java.io.Reader)(f: java.io.Reader => T): T = f(in)
61+
}
62+
63+
implicit object readFile extends Readable[File] {
64+
def read[T](file: File)(f: java.io.Reader => T): T = {
65+
val in = new java.io.BufferedReader(new java.io.FileReader(file))
66+
try
67+
f(in)
68+
finally
69+
in.close()
4570
}
46-
47-
def buffer: Sink[Array[Byte]] =
48-
new Sink[Array[Byte]] {
49-
def apply(op: OutputStream => Unit) = {
50-
val output = new java.io.ByteArrayOutputStream
51-
stream(output)(op)
52-
output.close()
53-
output.toByteArray
54-
}
71+
}
72+
73+
implicit object readString extends Readable[String] {
74+
def read[T](s: String)(f: java.io.Reader => T): T = {
75+
val in = new java.io.StringReader(s)
76+
try
77+
f(in)
78+
finally
79+
in.close()
5580
}
81+
}
5682
}
5783

58-
trait Source {
59-
def apply[T](op: InputStream => T): T
84+
trait Decoder[+T] {
85+
def decode[U : Decodable](source: U): T =
86+
implicitly[Decodable[U]].decode(source)(decodeFrom(_))
87+
def decodeFrom(source: java.io.InputStream): T
6088
}
6189

62-
object Source {
63-
implicit def stream(in: InputStream): Source =
64-
new Source {
65-
def apply[T](op: InputStream => T): T = op(in)
66-
}
90+
trait Encoder[-T] {
91+
def buffer(t: T): Array[Byte] = encode(t, ())(Encodable.encodeBytes)
6792

68-
implicit def file(name: String): Source =
69-
file(new java.io.File(name))
93+
def format(t: T): String = new String(buffer(t))
7094

71-
implicit def file(file: File): Source =
72-
new Source {
73-
def apply[T](op: InputStream => T): T = {
74-
val input =
75-
new java.io.BufferedInputStream(new java.io.FileInputStream(file))
76-
val res = stream(input)(op)
77-
input.close()
95+
def encode[Spec, Out](t: T, spec: Spec)(implicit encodable: Encodable[Spec, Out]): Out =
96+
implicitly[Encodable[Spec, Out]].encode(spec)(encodeTo(_, t))
7897

79-
res
80-
}
81-
}
98+
def encodeTo(sink: java.io.OutputStream, t: T): Unit
99+
}
82100

83-
def string(data: String): Source =
84-
new Source {
85-
def apply[T](op: InputStream => T): T = {
86-
val input = new java.io.ByteArrayInputStream(data.getBytes())
87-
val res = stream(input)(op)
88-
input.close()
89-
res
90-
}
91-
}
101+
trait Codec[T] extends Encoder[T] with Decoder[T]
92102

93-
def buffer(data: Array[Byte]): Source =
94-
new Source {
95-
def apply[T](op: InputStream => T): T = {
96-
val input = new java.io.ByteArrayInputStream(data)
97-
val res = stream(input)(op)
98-
input.close()
103+
trait Decodable[T] {
104+
def decode[U](t: T)(op: java.io.InputStream => U): U
105+
}
99106

100-
res
101-
}
107+
trait Encodable[Spec, Out] {
108+
def encode(spec: Spec)(op: java.io.OutputStream => Unit): Out
109+
}
110+
111+
object Encodable {
112+
implicit object encodeOutputStream extends Encodable[java.io.OutputStream, Unit] {
113+
def encode(spec: java.io.OutputStream)(op: java.io.OutputStream => Unit): Unit = op(spec)
114+
}
115+
116+
implicit object encodeBytes extends Encodable[Unit, Array[Byte]] {
117+
def encode(spec: Unit)(op: java.io.OutputStream => Unit): Array[Byte] = {
118+
val buff = new java.io.ByteArrayOutputStream
119+
try {
120+
op(buff)
121+
buff.toByteArray
122+
} finally
123+
buff.close()
124+
}
125+
}
126+
}
127+
128+
object Decodable {
129+
implicit object decodeDecoder extends Decodable[java.io.InputStream] {
130+
def decode[T](in: java.io.InputStream)(f: java.io.InputStream => T): T = f(in)
131+
}
132+
133+
implicit object decodeFile extends Decodable[File] {
134+
def decode[T](file: File)(f: java.io.InputStream => T): T = {
135+
val in = new java.io.BufferedInputStream(new java.io.FileInputStream(file))
136+
try
137+
f(in)
138+
finally
139+
in.close()
140+
}
141+
}
142+
143+
implicit object decodeString extends Decodable[Array[Byte]] {
144+
def decode[T](bs: Array[Byte])(f: java.io.InputStream => T): T = {
145+
val in = new java.io.ByteArrayInputStream(bs)
146+
try
147+
f(in)
148+
finally
149+
in.close()
102150
}
151+
}
103152
}
Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package org.geoscript
22
package geometry
33

4-
import org.geoscript.serialize.{ Sink, Source }
54
import org.scalatest._, matchers._
65

76
class SerializationSpec extends FunSuite with ShouldMatchers {
87
test("round-trip points") {
98
val p = point(100, 0)
10-
val json = GeoJSON.write(p, Sink.string)
9+
val json = GeoJSON.format(p)
1110
json should be("""{"type":"Point","coordinates":[100,0.0]}""")
12-
GeoJSON.read(Source.string(json)) should be(p)
11+
GeoJSON.read(json) should be(p)
1312
// TODO: Implement equality for geometries
1413
}
1514

1615
test("round-trip linestrings") {
1716
val ls = lineString(Seq((100, 0), (101, 1)))
18-
GeoJSON.write(ls, Sink.string) should be
19-
("""{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""")
17+
GeoJSON.format(ls) should be(
18+
"""{"type":"LineString","coordinates":[[100,0.0],[101,1]]}""")
2019
}
2120

2221
test("round-trip polygons") {
@@ -31,15 +30,15 @@ class SerializationSpec extends FunSuite with ShouldMatchers {
3130
)
3231
)
3332

34-
GeoJSON.write(solid, Sink.string) should be(
33+
GeoJSON.format(solid) should be(
3534
"""{"type":"Polygon","coordinates":[[[100,0.0],[101,0.0],[101,1],[100,1],[100,0.0]]]}""")
36-
GeoJSON.write(withHoles, Sink.string) should be(
35+
GeoJSON.format(withHoles) should be(
3736
"""{"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]]]}""")
3837
}
3938

4039
test("round-trip a multipoint") {
4140
val mp = multiPoint(Seq((100.0, 0.0), (101.0, 1.0)))
42-
GeoJSON.write(mp, Sink.string) should be(
41+
GeoJSON.format(mp) should be(
4342
"""{"type":"MultiPoint","coordinates":[[100,0.0],[101,1]]}""")
4443
}
4544

@@ -49,7 +48,7 @@ class SerializationSpec extends FunSuite with ShouldMatchers {
4948
Seq((102, 2), (103, 3))
5049
))
5150

52-
GeoJSON.write(mls, Sink.string) should be(
51+
GeoJSON.format(mls) should be(
5352
"""{"type":"MultiLineString","coordinates":[[[100,0.0],[101,1]],[[102,2],[103,3]]]}""")
5453
}
5554

@@ -59,13 +58,18 @@ class SerializationSpec extends FunSuite with ShouldMatchers {
5958
(Seq((100, 0), (101, 0), (101, 1), (100, 1), (100, 0)),
6059
Seq(Seq((100.2, 0.2), (100.8, 0.2), (100.8, 0.8), (100.2, 0.8), (100.2, 0.2))))))
6160

62-
GeoJSON.write(mp, Sink.string) should be(
61+
GeoJSON.format(mp) should be(
6362
"""{"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]]]]}""")
6463
}
6564

6665
test("round-trip a GeometryCollection") {
6766
val gc = multi(Seq(point(100, 0), lineString(Seq((101, 0), (102, 1)))))
68-
GeoJSON.write(gc, Sink.string) should be(
67+
GeoJSON.format(gc) should be(
6968
"""{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[100,0.0]},{"type":"LineString","coordinates":[[101,0.0],[102,1]]}]}""")
7069
}
70+
71+
test("We should be able to stream JSON straight to a file") {
72+
val p = point(1, 2)
73+
GeoJSON.write(p, new java.io.File("/home/dwins/Blub.json"))
74+
}
7175
}

geoscript/src/test/scala/tutorial/BasicGeometry.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,10 @@ object BasicGeometry extends App {
5252
line.buffer(1)
5353

5454
// serialization
55-
import org.geoscript.serialize.{ Sink, Source }
56-
geometry.WKT.write(point, Sink.string)
57-
geometry.GeoJSON.write(point, Sink.string)
55+
geometry.WKT.format(point)
56+
geometry.GeoJSON.format(point)
5857

5958
// deserialization
60-
geometry.WKT.read(Source.string("POINT (30 10)"))
61-
geometry.GeoJSON.read(Source.string("POINT (30 10)"))
59+
geometry.WKT.read("POINT (30 10)")
60+
geometry.GeoJSON.read("POINT (30 10)")
6261
}

0 commit comments

Comments
 (0)