123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679(* Copyright (c) 2021-2022 Patrick Ferris <patrick@sirref.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*)moduleIntf=Geojson_intfmoduletypeS=Geojson_intf.SmoduletypeJson=Geojson_intf.Jsonletdecode_or_errfv=matchfvwithOkx->x|Error(`Msgm)->failwithmmoduleMake(J:Intf.Json)=structtypejson=J.tletbbox_to_json_or_emptybbox=Option.(ifis_somebboxthen[("bbox",J.arrayJ.float(getbbox))]else[])moduleGeometry=structtypejson=J.tletkeys_in_use=["type";"coordinates";"bbox"]letforeign_membersjson=matchJ.to_objjsonwith|Okassoc->List.filter(fun(k,_v)->not(List.memkkeys_in_use))assoc|Error_->[]modulePosition=struct(* We use a float array internally for performance *)typet=floatarrayletlngt=t.(0)letlatt=t.(1)letaltitudet=trySomet.(2)with_->Noneletv?altitude~lng~lat()=matchaltitudewith|Somef->[|lng;lat;f|]|None->[|lng;lat|]letequall1l2=letn1=Array.lengthl1andn2=Array.lengthl2inifn1<>n2thenfalseelseletrecloopi=ifi=n1thentrueelseifFloat.equal(Array.unsafe_getl1i)(Array.unsafe_getl2i)thenloop(succi)elsefalseinloop0letto_jsonarr=J.arrayJ.floatarrend(* Returns the float array of coordinates if all goes well for any Geometry type *)letparse_by_typejsonp_ctyp=match(J.findjson["type"],J.findjson["coordinates"])with|None,_->Error(`Msg("JSON should"^"have a key-value for `type' whilst parsing "^typ))|_,None->Error(`Msg"JSON should have a key-value for `coordinates'")|Sometyp,Somecoords->(Result.bind(J.to_stringtyp)@@funtyp->matchtypwith|twhent=typ->p_ccoords|t->Error(`Msg("Expected type of `"^typ^"' but got "^t)))modulePoint=structtypet=Position.tlettyp="Point"letposition=Fun.idletvposition=positionletparse_coordscoords=J.to_array(decode_or_errJ.to_float)coordsletbase_of_jsonjson=parse_by_typejsonparse_coordstypletto_json?bbox?(foreign_members=[])position=J.obj([("type",J.stringtyp);("coordinates",Position.to_jsonposition);]@bbox_to_json_or_emptybbox@foreign_members)endmoduleMultiPoint=structtypet=Point.tarraylettyp="MultiPoint"letcoordinates=Fun.idletvpositions=positionsletparse_coordscoords=tryJ.to_array(decode_or_errPoint.parse_coords)coordswithFailurem->Error(`Msgm)letbase_of_jsonjson=parse_by_typejsonparse_coordstypletto_json?bbox?(foreign_members=[])positions=J.obj([("type",J.stringtyp);("coordinates",J.arrayPosition.to_jsonpositions);]@bbox_to_json_or_emptybbox@foreign_members)endmoduleLineString=structtypet=Position.tarraylettyp="LineString"letcoordinates=Fun.idletv=Fun.idletparse_coordscoords=Result.bind(tryMultiPoint.parse_coordscoordswithFailurem->Error(`Msgm))@@funarr->ifArray.lengtharr<2thenError(`Msg"LineStrings should have two or more points")elseOkarrletbase_of_jsonjson=parse_by_typejsonparse_coordstypletto_json?bbox?(foreign_members=[])positions=J.obj([("type",J.stringtyp);("coordinates",J.arrayPosition.to_jsonpositions);]@bbox_to_json_or_emptybbox@foreign_members)endmoduleMultiLineString=structtypet=LineString.tarraylettyp="MultiLineString"letlines=Fun.idletv=Fun.idletto_positions=Fun.idletof_positions=Fun.idletparse_coordscoords=tryJ.to_array(decode_or_errLineString.parse_coords)coordswithFailurem->Error(`Msgm)letbase_of_jsonjson=parse_by_typejsonparse_coordstypletto_json?bbox?(foreign_members=[])positions=J.obj([("type",J.stringtyp);("coordinates",J.array(J.array(J.arrayJ.float))positions);]@bbox_to_json_or_emptybbox@foreign_members)endmodulePolygon=structtypet=LineString.tarraylettyp="Polygon"letrings=Fun.idletexterior_ringt=t.(0)(* If used a lot, should changed to cstruct style off and len
to avoid the allocations here. *)letinterior_ringst=Array.subt1(Array.lengtht-1)letv=Fun.idletto_positions=Fun.idletof_positions=Fun.idletparse_coordscoords=tryJ.to_array(decode_or_err(J.to_array(decode_or_err(J.to_array(decode_or_errJ.to_float)))))coordswithFailurem->Error(`Msgm)letbase_of_jsonjson=parse_by_typejsonparse_coordstypletto_json?bbox?(foreign_members=[])positions=J.obj([("type",J.stringtyp);("coordinates",J.array(J.array(J.arrayJ.float))positions);]@bbox_to_json_or_emptybbox@foreign_members)endmoduleMultiPolygon=structtypet=Polygon.tarraylettyp="MultiPolygon"letpolygons=Fun.idletv=Fun.idletto_positions=Fun.idletof_positions=Fun.idletparse_coordscoords=tryJ.to_array(decode_or_errPolygon.parse_coords)coordswithFailurem->Error(`Msgm)letbase_of_jsonjson=parse_by_typejsonparse_coordstypletto_json?bbox?(foreign_members=[])positions=J.obj([("type",J.stringtyp);("coordinates",J.array(J.array(J.array(J.arrayJ.float)))positions);]@bbox_to_json_or_emptybbox@foreign_members)endtypegeometry=|PointofPoint.t|MultiPointofMultiPoint.t|LineStringofLineString.t|MultiLineStringofMultiLineString.t|PolygonofPolygon.t|MultiPolygonofMultiPolygon.t|Collectionoftlistandt=geometry*(string*json)listletrecbase_of_jsonjson=letfm=foreign_membersjsoninmatchJ.findjson["type"]with|Sometyp->(matchJ.to_stringtypwith|Ok"Point"->Result.map(funv->(Pointv,fm))@@Point.base_of_jsonjson|Ok"MultiPoint"->Result.map(funv->(MultiPointv,fm))@@MultiPoint.base_of_jsonjson|Ok"LineString"->Result.map(funv->(LineStringv,fm))@@LineString.base_of_jsonjson|Ok"MultiLineString"->Result.map(funv->(MultiLineStringv,fm))@@MultiLineString.base_of_jsonjson|Ok"Polygon"->Result.map(funv->(Polygonv,fm))@@Polygon.base_of_jsonjson|Ok"MultiPolygon"->Result.map(funv->(MultiPolygonv,fm))@@MultiPolygon.base_of_jsonjson|Ok"GeometryCollection"->(matchJ.findjson["geometries"]with|Somelist->letgeo=J.to_list(decode_or_errbase_of_json)listinResult.map(funv->(Collectionv,fm))geo|None->Error(`Msg"A geometry collection should have a member called \
geometries"))|Oktyp->Error(`Msg("Unknown type of geometry "^typ))|Error_ase->e)|None->Error(`Msg"A Geojson text should contain one object with a member `type`.")letrecto_json?bbox=function|Pointpoint,foreign_members->Point.to_json?bbox~foreign_memberspoint|MultiPointmp,foreign_members->MultiPoint.to_json?bbox~foreign_membersmp|LineStringls,foreign_members->LineString.to_json?bbox~foreign_membersls|MultiLineStringmls,foreign_members->MultiLineString.to_json?bbox~foreign_membersmls|Polygonp,foreign_members->Polygon.to_json?bbox~foreign_membersp|MultiPolygonmp,foreign_members->MultiPolygon.to_json?bbox~foreign_membersmp|Collectionc,foreign_members->J.obj([("type",J.string"GeometryCollection");("geometries",J.listto_jsonc);]@bbox_to_json_or_emptybbox@foreign_members)letforeign_members(_,fm)=fmletgeometry(g,_)=gletv?(foreign_members=[])g=(g,foreign_members)endmoduleFeature=structtypet={geometry:Geometry.toption;properties:jsonoption;foreign_members:(string*json)list;id:[`Stringofstring|`Floatoffloat]option;}letv?id?properties?(foreign_members=[])geo={geometry=Somegeo;properties;foreign_members;id}letgeometryt=t.geometryletpropertiest=t.propertiesletkeys_in_use=["type";"geometry";"properties";"id";"bbox"]letforeign_membersjson=matchJ.to_objjsonwith|Okassoc->List.filter(fun(k,_v)->not(List.memkkeys_in_use))assoc|Error_->[]letid_of_json=function|Somejson->(matchJ.to_stringjsonwith|Oks->Ok(Some(`Strings))|_->(matchJ.to_floatjsonwith|Okf->Ok(Some(`Floatf))|_->Error(`Msg"Identifier is not a string or number")))|None->OkNoneletid_to_json=function`Strings->J.strings|`Floatf->J.floatfletbase_of_jsonjson=matchJ.findjson["type"]with|Sometyp->(matchJ.to_stringtypwith|Ok"Feature"->(letfm=foreign_membersjsoninmatch(J.findjson["geometry"],J.findjson["properties"],J.findjson["id"])with|Somegeometry,properties,id->Result.bind(id_of_jsonid)(funid->Result.map(funv->{geometry=Option.somev;properties;foreign_members=fm;id;})(Geometry.base_of_jsongeometry))|None,properties,id->Result.map(funid->{geometry=None;properties;foreign_members=fm;id})(id_of_jsonid))|Oks->Error(`Msg("A Geojson feature requires the type `Feature`. Found type, \
but it was "^s))|Error_ase->e)|None->Error(`Msg"A Geojson feature requires the type `Feature`. No type was \
found.")letto_json?bbox{geometry;properties;foreign_members;id}=J.obj([("type",J.string"Feature")]@(matchgeometrywith|Somep->[("geometry",Geometry.to_jsonp)]|None->[])@(matchpropertieswithSomep->[("properties",p)]|None->[])@(matchidwithSomes->[("id",id_to_jsons)]|None->[])@bbox_to_json_or_emptybbox@foreign_members)letforeign_memberst=t.foreign_membersletidt=t.idmoduleCollection=structtypefeature=ttypenonrect={features:featurelist;foreign_members:(string*json)list;}letfeaturest=t.featuresletv?(foreign_members=[])features={features;foreign_members}letkeys_in_use=["type";"features";"geometry";"properties";"id";"bbox"]letforeign_membersjson=matchJ.to_objjsonwith|Okassoc->List.filter(fun(k,_v)->not(List.memkkeys_in_use))assoc|Error_->[]letbase_of_jsonjson=matchJ.findjson["type"]with|Sometyp->(matchJ.to_stringtypwith|Ok"FeatureCollection"->(letfm=foreign_membersjsoninmatchJ.findjson["features"]with|Somefeatures->letfeatures=J.to_list(fungeometry->decode_or_errbase_of_jsongeometry)featuresinResult.map(funv->{features=v;foreign_members=fm})features|None->Error(`Msg"A feature collection should have a member called \
`features`."))|Oks->Error(`Msg("A Geojson feature collection requires the type \
`FeatureCollection`. Found type, but it was "^s))|Error_ase->e)|None->Error(`Msg"A Geojson feature collection requires the type \
`FeatureCollection`. No type was found.")letto_json?bbox{features;foreign_members}=J.obj([("type",J.string"FeatureCollection");("features",J.listto_jsonfeatures);]@bbox_to_json_or_emptybbox@foreign_members)letforeign_memberst=t.foreign_membersendendtypegeojson=|FeatureofFeature.t|FeatureCollectionofFeature.Collection.t|GeometryofGeometry.tandt={geojson:geojson;bbox:floatarrayoption}letgeojsont=t.geojsonletbboxt=t.bboxletv?bboxgeojson={geojson;bbox}letgeojson_to_tgjsonbbox={geojson=gjson;bbox}letjson_to_bboxjson=matchJ.to_array(decode_or_errJ.to_float)jsonwith|Okv->Somev|Error_->Noneletof_jsonjson=match(J.findjson["type"],J.findjson["bbox"])with|Sometyp,bbx->(matchJ.to_stringtypwith|Ok"Feature"->(matchFeature.base_of_jsonjsonwith|Okv->Ok(geojson_to_t(Featurev)@@Option.bindbbxjson_to_bbox)|Errore->Errore)|Ok"FeatureCollection"->(matchFeature.Collection.base_of_jsonjsonwith|Okv->Ok(geojson_to_t(FeatureCollectionv)@@Option.bindbbxjson_to_bbox)|Errore->Errore)|Ok_maybe_geometry->(matchGeometry.base_of_jsonjsonwith|Okv->Ok(geojson_to_t(Geometryv)@@Option.bindbbxjson_to_bbox)|Errore->Errore)|Error_ase->e)|None,_->Error(`Msg"A Geojson text should contain one object with a member `type`.")letto_json=function|{geojson=Featuref;bbox}->Feature.to_json?bboxf|{geojson=FeatureCollectionfc;bbox}->Feature.Collection.to_json?bboxfc|{geojson=Geometryg;bbox}->Geometry.to_json?bboxgmoduleAccessor=structmoduleOptics=OpticsincludeOptics.Infixletget=Optics.Lens.getletgeojson=Optics.Lens.V((funt->(t.geojson,t)),fun(geojson,t)->{twithgeojson})letbbox=Optics.Lens.V((funt->(t.bbox,t)),fun(bbox,t)->{twithbbox})letfeature=letinto=function|Featuref->Okf|v->ignore(failwith"Big yikes");Errorvinletout_of=functionOkf->Featuref|Errorv->vinOptics.Prism.V(into,out_of)letfeature_collection=letinto=functionFeatureCollectionf->Okf|v->Errorvinletout_of=functionOkf->FeatureCollectionf|Errorv->vinOptics.Prism.V(into,out_of)letgeometry=letinto=functionGeometryf->Okf|v->Errorvinletout_of=functionOkf->Geometryf|Errorv->vinOptics.Prism.V(into,out_of)moduleFeature=structletproperties=Optics.Lens.V((funt->(t.Feature.properties,t)),fun(properties,t)->{twithproperties})letforeign_members=Optics.Lens.V((funt->(t.Feature.foreign_members,t)),fun(foreign_members,t)->{twithforeign_members})letgeometry=Optics.Lens.V((funt->(t.Feature.geometry,t)),fun(geometry,t)->{twithgeometry})letgeometry_exn=Optics.Lens.V((funt->(Option.gett.Feature.geometry,t)),fun(geometry,t)->{twithgeometry=Somegeometry})endmoduleGeometry=structletgeometry:(Geometry.t,Geometry.geometry)Optics.Lens.t=Optics.Lens.fstletforeign_members:(Geometry.t,(string*json)list)Optics.Lens.t=Optics.Lens.sndletpoint:(Geometry.geometry,Geometry.Point.t)Optics.Prism.t=letinto=functionGeometry.Pointf->Okf|v->Errorvinletout_of=functionOkf->Geometry.Pointf|Errorv->vinOptics.Prism.V(into,out_of)letmultipoint:(Geometry.geometry,Geometry.MultiPoint.t)Optics.Prism.t=letinto=functionGeometry.MultiPointf->Okf|v->Errorvinletout_of=functionOkf->Geometry.MultiPointf|Errorv->vinOptics.Prism.V(into,out_of)letlinestring:(Geometry.geometry,Geometry.LineString.t)Optics.Prism.t=letinto=functionGeometry.LineStringf->Okf|v->Errorvinletout_of=functionOkf->Geometry.LineStringf|Errorv->vinOptics.Prism.V(into,out_of)letmultilinestring:(Geometry.geometry,Geometry.MultiLineString.t)Optics.Prism.t=letinto=function|Geometry.MultiLineStringf->Okf|v->Errorvinletout_of=function|Okf->Geometry.MultiLineStringf|Errorv->vinOptics.Prism.V(into,out_of)letpolygon:(Geometry.geometry,Geometry.Polygon.t)Optics.Prism.t=letinto=functionGeometry.Polygonf->Okf|v->Errorvinletout_of=functionOkf->Geometry.Polygonf|Errorv->vinOptics.Prism.V(into,out_of)letmultipolygon:(Geometry.geometry,Geometry.MultiPolygon.t)Optics.Prism.t=letinto=functionGeometry.MultiPolygonf->Okf|v->Errorvinletout_of=function|Okf->Geometry.MultiPolygonf|Errorv->vinOptics.Prism.V(into,out_of)endendmoduleRandom=structtypegeometry=|Point|MultiPointofint|LineStringofint|MultiLineStringofint*int|Polygonofint|MultiPolygonofint*int|Collectionofgeometrylisttypefeature={properties:jsonoption;geometry:geometry}typer=FCoffeaturelist|Foffeature|Gofgeometryletrandom~ft=letrecaux_random=function|FCfs->letfeatures=List.maprandom_ffsin{geojson=FeatureCollection{features;foreign_members=[]};bbox=None;}|Ff->{geojson=Feature(random_ff);bbox=None}|Gg->{geojson=Geometry(random_gg);bbox=None}andrandom_f{properties;geometry}=letgeo=random_ggeometryin{geometry=Somegeo;properties;foreign_members=[];id=None}andrandom_g=function|Point->(Geometry.Point(random_point()),[])|MultiPointi->(Geometry.MultiPoint(Array.initi(fun_->random_point())),[])|LineStringi->(Geometry.LineString(Array.initi(fun_->random_point())),[])|MultiLineString(i,j)->(Geometry.MultiLineString(Array.initi@@fun_->Array.initj(fun_->random_point())),[])|Polygoni->(Geometry.Polygon(random_polygoni),[])|MultiPolygon(i,j)->letarr=Array.initi(fun_->random_polygonj)in(Geometry.MultiPolygonarr,[])|Collectionlst->letlst=List.maprandom_glstin(Geometry.Collectionlst,[])andrandom_point()=Geometry.(Point.v(Position.v~lat:(f())~lng:(f())()))andrandom_polygoni=(* This geometry is not going to be very country like... *)letpoints=Array.initi(fun_->random_point())inpoints.(i-1)<-points.(0);[|points|]inaux_randomtendend