A collection of libraries in pure OCaml for parsing, constructing, and manipulating TopoJSON objects.
TopoJSON encodes geospatial data using topological information to improve how this data is encoded. In many ways
it is similar to GeoJSON, using similar primitives like a Point
or a Multipolygon
. However, the primary way
to use TopoJSON is with a shared Topology. This is an object that also contains arcs. An arcs field contains
an array of an array of positions essentially a collection of linestrings. Other geometries can then index into
this database of segments to build themselves up. This can help reduce the size of the data significantly as often
geometries share segments (e.g. country borders).
For a full coverage of TopoJSON be sure to read the specification.
This library follows many of the same principles as ocaml-geojson. It might be useful to have a read of that README to understand the structure of the code.
The first thing to do is initialise the Topojson
module with a JSON parsing implementation. For these examples we'll use Ezjsonm, the parser can be found in doc/prelude.txt
.
module Topojson = Topojson.Make (Ezjsonm_parser);;
open Topojson
We don't restrict ourself to a single JSON parser implementation as it allows us to use the best one depending on where the code is run (e.g. the browser's built-in parser).
We can describe a TopoJSON object using the Ezjsonm primitives.
let topojson : Ezjsonm.value =
`O [
("type", `String "Topology");
("objects" , `O [ ("Instance" , `O [("type", `String "Polygon"); ("arcs", `A [ `A [`Float 0.]]) ]) ]) ;
("arcs", `A [ `A [ `A [`Float 100.;`Float 0.]; `A [`Float 101.; `Float 0.]; `A [`Float 101.; `Float 1.]; `A [`Float 100.; `Float 1.]; `A [`Float 100.; `Float 0.]]] );
]
This is quite verbose but you can see how it is structured. First, we declare that the kind of TopoJSON object we are
building is a "Topology"
so we must provide a "objects"
field and an "arcs"
field. From this Ezjsonm value we can
read it into a Topojson.t
.
# let t1 = Topojson.of_json topojson |> Result.get_ok;;
val t1 : t = <abstr>
We could just as easily build the same TopoJSON value using the constructors provided by the library.
# let arcs = [| [| Geometry.Position.v ~lng:100. ~lat:0. (); Geometry.Position.v ~lng:101. ~lat:0. (); Geometry.Position.v ~lng:101. ~lat:1. (); Geometry.Position.v ~lng:100. ~lat:1. (); Geometry.Position.v ~lng:100. ~lat:0. () |] |];;
val arcs : Geometry.Position.t array array =
[|[|<abstr>; <abstr>; <abstr>; <abstr>; <abstr>|]|]
# let instance = Geometry.(polygon [| LineString.v (Arc_index.v [ 0 ]) |]);;
val instance : Geometry.geometry = Topojson.Geometry.Polygon <abstr>
# let topology = Topology.v ~arcs [ "Instance", Geometry.v instance ];;
val topology : Topology.t = <abstr>
# let t2 = Topojson.(v (Topology topology));;
val t2 : t = <abstr>
# t1 = t2;;
- : bool = true
Consider the following TopoJSON object as a string.
# let topojson_string = {|{
"arcs": [[[0.0, 0.0], [0.0, 9999.0], [2000.0, 0.0], [0.0, -9999.0], [-2000.0, 0.0]]],
"objects": {"example ": {
"type": "GeometryCollection",
"geometries": [
{"coordinates": [4000.0, 5000.0],
"properties": {"prop0": "value0"},
"type": "Point"},
{"arcs": [[0]],
"properties": {"prop0": "value0", "prop1": {"this": "that"}},
"type": "Polygon"}
]
}},
"transform": {"scale": [0.0005, 0.0001], "translate": [100.0, 0.0]},
"type": "Topology",
"extra": [ "Wow!" ]}|};;
val topojson_string : string =
"{\n \"arcs\": [[[0.0, 0.0], [0.0, 9999.0], [2000.0, 0.0], [0.0, -9999.0], [-2000.0, 0.0]]],\n \"objects\": {\"example \": {\n \"type\": \"GeometryCollection\",\n \"geometries\": [\n {\"coordinates\": [4000.0, 5000.0],\n \"properties\": {\"prop0\": \"value0\"},\n "... (* string length 620; truncated *)
You can make use of the TopoJSON function Topojson.of_json
after converting the string to your JSON parser's
format.
# let topojson =
Result.get_ok @@
Topojson.of_json @@
Ezjsonm.value_from_string topojson_string;;
val topojson : t = <abstr>
Given the TopoJSON object above, we can extract the foreign members by using the foreign_member
function.
But first we must confirm that it is a topology.
# match Topojson.topojson topojson with
| Geometry _ -> failwith "Expected a topology!"
| Topology t -> Topology.foreign_members t;;
- : (string * json) list = [("extra", `A [`String "Wow!"])]