Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extract functions (Geojson to Topojson) #46

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

Priceless-P
Copy link
Contributor

Extracts lines and rings from Geojson's Objects

@Priceless-P
Copy link
Contributor Author

@patricoferris what do i think about this? I also intend to add extract_geomteries and extract_features_collection (if necessary).

loop [] coordinates
| _ -> failwith "Not a valid LineString or Polygon"

let extract_lines = function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment applies to pretty much most of the other functions defined here.

I don't think we want to reimplement all of the JSON parsing logic again, GeoJSON does this already for us so we should just uses GeoJSON to extract the coordinates etc. from the geometries (since we are converting from GeoJSON to TopoJSON).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -282,6 +282,21 @@ module type S = sig

val to_json : ?bbox:float array -> t -> json
val of_json : json -> (t, [ `Msg of string ]) result

val extract_lines :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We won't actually ever expose these internal functions. Instead, eventually, we will just have something like:

val geojson_to_topojson : Geojson.t -> Topojson.t

@Priceless-P
Copy link
Contributor Author

Hi @patricoferris,

module Geojson = Geojson.Make (Ezjsonm_parser)

keep giving error Unbound module Ezjsonm_parser

Also with the current implementation, will extract_lines_from_geometry also extract rings from Polgon and MultiPolygon?

@patricoferris
Copy link
Contributor

patricoferris commented Feb 11, 2023

Also with the current implementation, will extract_lines_from_geometry also extract rings from Polgon and MultiPolygon?

I think so, they are after all just lines.

keep giving error Unbound module Ezjsonm_parser

Yep, we still want to be agnostic to the JSON parser the user decided to use. You should be able to pass the same one that gets passed to our Make "functor". This is a special name for modules that are parameterised by other modules.

module Make (J : Intf.Json) = struct
  module Geojson = Geojson.Make(J)
  ...

@Priceless-P
Copy link
Contributor Author

Hi @patricoferris what do you think?

let arr = Array.map LineString.coordinates lines in
Array.to_list arr
| Polygon p ->
let rings = Polygon.rings p in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm thinking about this a little more, perhaps we have to treat rings slightly differently because the first and last point are technically connected.

List.iter
(fun line ->
let line_length = Array.length line in
for i = 0 to line_length - 2 do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Points on a line other than the first and the last have two neighbours: n - 1 and n + 1.

let find_junctions (lines : Geojson.Geometry.Position.t array list) :
Geojson.Geometry.Position.t list =
let open Geojson.Geometry in
let junction_table : (Position.t, Position.t list) Hashtbl.t =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make this a position to position set hash table to stay closer to how the blog post does it?

for i = 0 to line_length - 2 do
let point1 = line.(i) in
let point2 = line.(i + 1) in
if Hashtbl.mem junction_table point1 then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for every point we visit we want to put in the hash table it's neighbours as a set. So if we are at point N then we but N, { N - 1, N - 2} as a key-value pair into the hash table.

Then if we come across N again we check that Ns neighbours are different (therefore a junction) or if they are the same then it isn't.

@Priceless-P
Copy link
Contributor Author

@patricoferris I am having a lot of type incompatibility errors. Please help me out 😥

@patricoferris
Copy link
Contributor

Ah, most of the issues were just related to giving functions a little too much generality and also we didn't expose that the positions were float arrays. Feel free to cherry-pick this commit 7b31814

@Priceless-P
Copy link
Contributor Author

The type errors are gone now. :)) Thank you @patricoferris

@Priceless-P
Copy link
Contributor Author

Do you think the find_junction is correction? I changed the MultiLineString coordinates to

 "coordinates": [
        [
			[1, 1], [2, 2],[3, 3],
			[4, 4], [3, 1],[4, 2]

        ], [
			[4, 2], [4, 4], [4, 6]
        ]
    ]

And the test didn't print out any junctions

@patricoferris
Copy link
Contributor

I don't think the function is correct, see my comments inline on the code.

Copy link
Contributor

@patricoferris patricoferris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are heading in the right direction, nice! Comments are inline.

@@ -127,7 +127,7 @@ let pp_transform ppf t =

let transform = Alcotest.testable pp_transform Stdlib.( = )

let to_string pp a =
let _to_string pp a =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this function isn't being used we can probably remove it ?

let point1 = line.(i) in
let point2 = line.(i - 1) in
let point3 = line.(i + 1) in
let point2 = if i > 0 then line.(i - 1) else line.(line_length - 1) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this only true if we're talking about a ring rather than any line?

let point2 = line.(i - 1) in
let point3 = line.(i + 1) in
let point2 = if i > 0 then line.(i - 1) else line.(line_length - 1) in
let point3 = if i < line_length - 1 then line.(i + 1) else line.(0) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here?

@@ -698,24 +696,23 @@ module Make (J : Intf.Json) = struct
let junction_table : (Position.t, Set.t) Hashtbl.t =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really want to know every set of neighbours to begin with (let's get the algorithm correct before optimising). So I think we want something like: (Position.t, Set.t list) Hashtbl.t.

let point2 = line.(i - 1) in
let point3 = line.(i + 1) in
let point2 = if i > 0 then line.(i - 1) else line.(line_length - 1) in
let point3 = if i < line_length - 1 then line.(i + 1) else line.(0) in
if Hashtbl.mem junction_table point1 then
let neighbors = Hashtbl.find junction_table point1 in
Hashtbl.replace junction_table point1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding the neighbours to an ever growing set, I think we want to keep them distinct so we know what two neighbours belong together. Consider the problem of having two lines ABC and CBA. B in this case is not a junction because the neighbours A and C are the same in both lines.

if Hashtbl.mem junction_table point1 then
let neighbors = Hashtbl.find junction_table point1 in
Hashtbl.replace junction_table point1
(Set.add point2 (Set.add point3 neighbors))
else
Hashtbl.add junction_table point1 (Set.of_list [ point2; point3 ]);
let neighbors = Hashtbl.find junction_table point1 in
if Set.cardinal neighbors > 2 then
ref junction := point1 :: !(ref junction)
if Set.cardinal neighbors > 2 then junction := point1 :: !junction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the above changes this will become List.length when we store the list of possible neighbours and store a new pair iff they are different as defined in the algorithm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants