madeinQuant
madeinQuant

Reputation: 1813

How to decode GeoJson in ReasonML?

Please comment how to decode a GeoJson file in ReasonML? I try to decode coordinates without "field latitude & longitude" in decoder, but I cannot find any information how to parse the field coordinates in the JSON file.

GeoJson file

 "features": [
     {
       "type": "Feature",
       "geometry": {
         "type": "Point",
         "coordinates": [
           131.469670264,
           33.3158712032
         ]
       },
       "properties": {
         "index": 0,
         "alias": "海地獄-別府市",
         "name": "Umi-Jigoku",
         "image_url": "https://s3-media1.fl.yelpcdn.com/bphoto/7T1aXG9Q3CAtEbwqFm3Nlw/o.jpg"
       }

JsonDecoder (bs-json) in ReasonML

[@genType]
type properties = {
  index: int,
  alias: string,
  name: string,
  image_url: string,
  geometry: coordinates,
}
and coordinates = {
  latitude: float,
  longitude: float,
};

let places = "../json/alljapan.json";

module Decode = {
  let coordinates = json =>
    Json.Decode.{
      latitude: json |> field("latitude", float),
      longitude: json |> field("longitude", float),
    };

  let properties = json =>
    Json.Decode.{
      index: json |> field("index", int),
      alias: json |> field("alias", string),
      name: json |> field("name", string),
      image_url: json |> field("image_url", string),
      geometry: json |> field("geometry", coordinates),
    };
};

let line = places |> Json.parseOrRaise |> Decode.line;

Upvotes: 1

Views: 422

Answers (2)

glennsl
glennsl

Reputation: 29106

You should probably use GeoReason, as @mlms13 suggests, but if you still want to decode it yourself using bs-json you can exploit the fact that tuples are implemented as arrays in BuckleScript and use the pair decoder to get the values, then map that to your record type:

let coordinates = json =>
  Json.Decode.(
    pair(float, float)
    |> map(((latitude, longitude)) => {latitude, longitude})
  );

Upvotes: 2

Michael Martin-Smucker
Michael Martin-Smucker

Reputation: 12705

At work, we wrote a library called GeoReason with the goal of providing Reason types, constructors, encoders, and decoders (along with some helper functions such as eq) for GeoJSON data structures, following the RFC-7946 spec.

I haven't tried using the library with React Native, but I assume it should work anywhere you can compile Reason to JS.

Overview

  • GeoJSON models a lot of "or" (Feature or Geometry, where Geometry is Point or Line or...) relationships, which are easy to express as Reason variants
  • GeoJSON is compact, preferring arrays (as tuples), which is hard to read. We use Reason records for this.
  • We decode values using bs-decode
  • "Features" come with extra metadata (the "properties" field), which can be any JSON object with metadata... We don't attempt to decode this beyond a key => json dictionary: Js.Dict.t(Js.Json.t)

Usage

Assuming you have a JSON value of type Js.Json.t, and you've installed GeoReason following the instructions in the README, you could decode and use your data like this:

// this is a `Belt.Result` that is either `Ok` with the GeoJSON data, or
// Error with information describing what went wrong while decoding
let decoded = GeoJSON.decode(myFileData);

switch (decoded) {
| Error(parseError) =>
  Decode.ParseError.failureToDebugString(parseError) |> Js.log;

// if the GeoJSON value is a "Feature", it may have the following fields,
// but note that technically all of them are optional according to the spec.
// if you want to decode the dictionary of properties, you can do so here
| Ok(GeoJSON.Feature({id, geometry, properties})) =>
  properties
  ->Belt.Option.flatMap(dict => Js.Dict.get(dict, "image_url"))
  ->Belt.Option.flatMap(json => Js.Json.decodeString(json))
  ->Belt.Option.map(url => /* do something with the url? */);

| Ok(Geometry(Point({latlong, altitude})) =>
  /* use latitude and longitude? */

| Ok(Geometry(Polygon(data))) => /* ... */

| Ok(_) =>
  // lots more cases to handle, like line geometries,
  // geometry collections, feature collections, etc...
};

As you can see, matching on all of the things a GeoJSON value could be is a fairly involved process, so it depends a bit on what you're hoping to do with the values. I've added some helpers to the library, such as GeoJSON.getPolygons, which will attempt to get a list of polygons for any kind of GeoJSON value (returning an empty list if there were no polygons). Feel free to open issues if there are additional helpers that you'd find useful.

Upvotes: 2

Related Questions