arthur-e / Wicket

A modest library for moving between Well-Known Text (WKT) and various framework geometries
https://arthur-e.github.io/Wicket/
Other
586 stars 226 forks source link

Multipolygon (include Polygon with holes) event listener #101

Closed trungvose closed 3 years ago

trungvose commented 7 years ago

Hi Arthur,

Firstly, thanks for your awesome library. I am implementing the UI for the user to input WKT and update it on the map. After that user is able to make the change on the map and the updated WKT value will get back to the text area accordingly. Just almost same with your google maps example.

https://arthur-e.github.io/Wicket/sandbox-gmaps3.html

Basically, your example didn't support the polygon with holes event such as insert_at, remove_at, set_at because the following code not allowing the user to edit polygon with holes.

// Add listeners for overlay editing events
if (!Wkt.isArray(obj) && wkt.type !== 'point') {
    // New vertex is inserted
    google.maps.event.addListener(obj.getPath(), 'insert_at', function (n) {
        app.updateText();
    });
    // Existing vertex is removed (insertion is undone)
    google.maps.event.addListener(obj.getPath(), 'remove_at', function (n) {
        app.updateText();
    });
    // Existing vertex is moved (set elsewhere)
    google.maps.event.addListener(obj.getPath(), 'set_at', function (n) {
        app.updateText();
    });
} else {
    if (obj.setEditable) {obj.setEditable(false);}
}

Understand that polygon with a hole in it has multiple paths, and we must listen to changes on both paths. E.g I am using your WKT

MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))

After call obj = wkt.toObject(this.gmap.defaults);, obj will become the array of 2 elements

I have applied the listener inside the for loop

if (Wkt.isArray(obj)) { // Distinguish multigeometries (Arrays) from objects
    for (i in obj) {
        if (obj.hasOwnProperty(i) && !Wkt.isArray(obj[i])) {
            obj[i].setMap(this.gmap);
            this.features.push(obj[i]);
            this.addPolygonListener(obj[i])
            if(wkt.type === 'point' || wkt.type === 'multipoint')
                bounds.extend(obj[i].getPosition());
            else
                obj[i].getPath().forEach(function(element,index){bounds.extend(element)});
        }
    }

    this.features = this.features.concat(obj);
}

private addPolygonListener(polyData) {
    let self = this;
    // New vertex is inserted
    google.maps.event.addListener(polyData.getPath(), 'insert_at', function (n) {
        self.updateText();
    });
    // Existing vertex is removed (insertion is undone)
    google.maps.event.addListener(polyData.getPath(), 'remove_at', function (n) {
        self.updateText();
    });
    // Existing vertex is moved (set elsewhere)
    google.maps.event.addListener(polyData.getPath(), 'set_at', function (n) {
        self.updateText();
    });

    google.maps.event.addListener(polyData.getPaths(), 'click', () => {
        debugger
    });
}

I think you know the result for the second element with holes, only the outer polygon worked because I saw polyData.getPath() only return the value for one array of the outer element. I have also tried polyData.getPaths() - getPaths for returning an array of both outer and inner element. Let call it as polyHolesArray. Then when I do the for loop of polyHolesArray to apply addPolygonListener() into its element. But they don't contain the method getPath().

Hope you understand my explain above. So I have two question.

  1. Is this feasible to listen to change of polygon with holes and extract it from google map to WKT. If yes, can you update your example?
  2. When I tried as I said to you above that outer polygon worked, I get some value as:

MULTIPOLYGON(((40 40,20 45,45 30,40 40)),((21.142578125 33.25399732319053,10 30,6.396484375 20.074580358450604,10 10,30 5,45 20,21.142578125 33.25399732319053),(20 25,20 15,30 20,20 25)))

If I copy this value to your example, the holes inside is becoming another polygon and the outer polygon now is solid. So what is possible of causing the error. The same result If I save it to database and load it again. See the image for before after saving

Before Before

After After

Understand from google doc

To create an empty area within a polygon, you need to create two paths, one inside the other. To create the hole, the coordinates defining the inner path must be in the opposite order to those defining the outer path. For example, if the coordinates of the outer path are in clockwise order then the inner path must be counter-clockwise.

But I didn't really get it. I will create the jsfiddle for my implementation later.

Thanks and have a nice day

arthur-e commented 7 years ago

Hi @trungk18! I'm not sure I follow your description exactly but the problem you're experiencing may be related to an outstanding issue, #33. The Google Maps API does not appear to have a reliable way of detecting when an inner ring is a hole versus when an inner ring is a polygon within a multipolygon, at least for concave geometries but possibly more general cases as well. Until that issue with the Google Maps API extension in Wicket is resolved, I'm not sure it is worthwhile to update the sandbox in the way you're suggesting.

trungvose commented 7 years ago

@arthur-e Thanks Authur for your reply. I am doing a small hack to reverse the order of polygon when saving. But do you have a proper method for detecting if a set of points in an array that are the vertices of a complex polygon were defined in a clockwise or counterclockwise order?

arthur-e commented 7 years ago

@trungk18 There used to be a tool in the Google Maps API to do just that; can't find it now. It wasn't working properly, anyway, as I recall, which is one of the reasons we're in this mess with Wicket. A quick Google search turned up this solution, which allegedly works for both convex and non-convex polygons.

trungvose commented 7 years ago

Thanks @arthur-e, I have another query because I am confusing on how wicket is working.

Let's say I have the WKT as below and a function to set it into the map.

POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))

SetPolygon(value: string): boolean {
    let polyOptions: any = {
        strokeWeight: 3,
        strokeColor: '#CC3399',
        fillColor: '#3333CC',
        fillOpacity: 0.45,
        editable: true
    };
    let bounds = new google.maps.LatLngBounds();
    let wkt = this.readWKT(value);
    let polyData = wkt.toObject(polyOptions); // Make an polyData
    //Single polygon handling
    polyData.setMap(this._map); // Add it to the map
    **this.features.push(polyData);**
    polyData.getPath().forEach(function (element, index) {
        bounds.extend(element)
    });
    // Pan the map to the feature
    this._map.fitBounds(bounds);
}

Result as before


After that, I have a function to read data from this.features array and converting back to WKT.

ConvertToWKT(value: string): boolean {
    let wkt = new Wkt.Wkt();

    wkt.fromObject(input);
    return wkt.write();
}

ConvertToWKT(this.features)

And the respond as

POLYGON((35 10,45 45,15 40,10 20,35 10),(30 20,35 35,20 30,30 20))

Noted that there are slightly changed on the second polygon which the inner ring. And that is the reason why these two polygons on the map are merging.

Result as after

So I am quite confusing why wicket change the ordering of point inside the inner ring, can you help me to explain that?