Click to select a sub-polygon from a Multi-Polygon

1193
7
Jump to solution
10-29-2018 11:33 AM
DanielBonan
New Contributor II

This is my very first question, so please forgive me if i'm not using best practice... here is my question:

I have an web app where people can draw polygons over a basemap (html based), the polygons are then stored in my sql server database as a WKT string ("MULTIPOLYGON(((lat long, lat long, lat long, ...)),((lat long, lat long, lat long, ...)), ...)").

Which means one multipolygon string has many sub-polygons which display as different shapes on the screen when the page re-loads.

Is there a way to click on one shape and identify which sub-polygon is being clicked (the goal is to be able to delete it from sql if shape was incorrectly done)?

e.g. the bold string below, being sub-polygon number 2 of the multipolygon (also shown on the image i attached):

MULTIPOLYGON (((-74.4897049011512 40.802801297423777, -74.489796096257678 40.802841904076779, -74.489778661899081 40.802862207393964, -74.489690149001618 40.80282160075339, -74.4897049011512 40.802801297423777)), ((-74.489702550924846 40.802796180105481, -74.489713279760892 40.8027789222691, -74.489821909225967 40.802829680598578, -74.4898071570764 40.802835771595511, -74.489702550924846 40.802796180105481)))

I was thinking something like getting the coordinates of the click and check if it falls inside any of the sub-polygon, but i am having a hard time trying to do it, and was wondering if ArcGis API had already something existing.

thanks

Dan.

0 Kudos
1 Solution

Accepted Solutions
DanielBonan
New Contributor II

Thank youDan Patterson and John Grayson , you brought me to the right path and help me understand the problem here. Actually I decided to handle this with Javascript and stay away from ArcGis functions, but still decomposing the polygons as you both explained. It may not be as elegant as your code but it works fine:
I load each single ring, add some attributes on the fly (id and type), then once loaded i can grab the type and id from the click (and the graphic geometry) to delete the string from the SQL record:

function addPolygon(){   


    var index, len;
    var gra = [];
    var polygonExplode = polygon.split("]],[[");
   


    if(polygonExplode.length > 1) {

      for(index = 0, len = polygonExplode.length; index < len; ++index){

         var singleRingPolygon = new Polygon (JSON.parse(polygonExplode));
          gra[index] = new Graphic(singleRingPolygon, sfs); 

          gra[index].ZIndex = 3;
          gra[index].PolygonId = index;
          gra[index].type = "1";
          map.graphics.add(gra[index]);

     }
  }

......

map.graphics.on("mouse-down", function(evt){
      if(evt.which == 3) {

      polygonID = evt.graphic.PolygonId;
      polygonString = JSON.stringify(evt.graphic.geometry.rings);
      btype = evt.graphic.type;


....... 


}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post

0 Kudos
7 Replies
DanPatterson_Retired
MVP Esteemed Contributor

There is no examples even in arcpy for arcmap or ArcGIS pro.

You could always make singlepart features as a separate layer if there is some compelling reason not to do this in the first place.  A click on the singlepart layer which could fire a select by location and identify which part in the multipart was 'clicked' by a 'point within' from the singlepart layer (note all will be selected, but only one will contain the point.

Not sure which would be easier in the long run. 

0 Kudos
DanielBonan
New Contributor II

Thanks for your reply, the number of records for my polygons are dictated by the database model, which is based on uniqueness (easier to maintain and keep clean).

Also other datasets opted for unique multi-polygon records, for instance Census has a unique multipolygon record for the town of Charleston, SC which is an aggregation of smaller shapes sometimes disconnected from each other. Since my database is constructed around those open data sets, i need to keep consistency in the way i store the data.

I am trying to wrap my head around what you are saying: Should i deconstruct the multi polygon into single parts layers before loading the graphics on the page? then let the users do their stuff and reconstruct it again before saving it back to SQL?

0 Kudos
DanPatterson_Retired
MVP Esteemed Contributor

Daniel... your stated goal is...

the goal is to be able to delete it from sql if shape was incorrectly done

Which means at some stage the multipart shape needs to be exploded into its single parts.  You can either query the multipart using John's code above, or query the single part representation … in either case, it is still a 'polygon contains' or 'point in polygon' as the final step.

The assembly stage of the remaining polygon parts will use the 'key' polygon id field, since any multipart to singlepart deconstruction will retain the original polygon id as a minimum.

You will then have to deal with 'dissolving' those parts back to a multipart feature to move it back into the database you are maintaining.

I all sounds pretty ugly, but never edit the original of course, and working from a multipart then removing a 'bit' or working with singleparts and 'reassembling' is probably a matter of workflow preference.

My preference would be to explode the multipart to single parts since you will have a geometry with more shapes, but some of those will have belonged to a multipart shape.

I would then 'click' (aka, make points) the features that you want to remove.  Once you are done identifying all the features that you want to toss. 

I would do a 'select by location' all those polygons that contain the points.... delete those... 'dissolve' the remain 'bits' on the 'original id field' and you now should have multipart polygons minus some fiddly bits that you wanted to get rid of.

Good luck

PS... I hope there are equivalents for this in Java.  This sounds like a data prep stage and ArcGIS PRo or ArcMap are way better suited to this sort of work.

I hope/suspect there are ArcToolbox (and/or arcpy, python) equivalents for these tasks on the other side of the coding world

DanielBonan
New Contributor II

Thanks a lot to you and John Grayson‌ for explanations, i'm gonna try to figure this out and get back here to let you know

0 Kudos
JohnGrayson
Esri Regular Contributor

Please note that not all polygons with multiple rings have multiple parts; for example a single water body with a single island is represented by as a single part polygon compose of multiple rings.  Each ring can represent a different part OR a different ring within a part.  When creating a list of single part polygons as Dan suggest above, please make sure you deal with these nuances correctly.  Below is what I'v used in the past to disassemble a polygon into a list of single part polygons.  You can then iterate over the list of polygons and simply call 'contains(...)' on each polygon.

multipart_to_singlepart: function (sourcePolygon) {

  if(sourcePolygon.rings.length > 1) {
    let originalPolygon = geometryEngine.simplify(sourcePolygon).clone();

    let holes = [];
    let polygons = [];
    for (let ringIndex = 0; ringIndex < originalPolygon.rings.length; ringIndex++) {
      let ring = originalPolygon.rings[ringIndex];
      if(ring.length >= 3) { // IGNORE RINGS WITH LESS THAN THREE VERTICES //
        if(originalPolygon.isClockwise(ring)) {
          polygons.push(new Polygon({ spatialReference: originalPolygon.spatialReference, rings: [ring] }));
        } else {
          holes.push(ring);
        }
      }
    }

    polygons.sort(function (geomA, geomB) {
      return geometryEngine.planarArea(geomA, "square-meters") - geometryEngine.planarArea(geomB, "square-meters");
    });

    for (let holeIndex = 0; holeIndex < holes.length; holeIndex++) {
      for (let geomIndex = 0; geomIndex < polygons.length; geomIndex++) {
        if(polygons[geomIndex].contains(new Point({ spatialReference: originalPolygon.spatialReference, x: holes[holeIndex][0][0], y: holes[holeIndex][0][1] }))) {
          polygons[geomIndex].addRing(holes[holeIndex]);
          break;
        }
      }
    }
    return polygons;
  } else {
    return [sourcePolygon];
  }
}
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
DanielBonan
New Contributor II

Thank youDan Patterson and John Grayson , you brought me to the right path and help me understand the problem here. Actually I decided to handle this with Javascript and stay away from ArcGis functions, but still decomposing the polygons as you both explained. It may not be as elegant as your code but it works fine:
I load each single ring, add some attributes on the fly (id and type), then once loaded i can grab the type and id from the click (and the graphic geometry) to delete the string from the SQL record:

function addPolygon(){   


    var index, len;
    var gra = [];
    var polygonExplode = polygon.split("]],[[");
   


    if(polygonExplode.length > 1) {

      for(index = 0, len = polygonExplode.length; index < len; ++index){

         var singleRingPolygon = new Polygon (JSON.parse(polygonExplode));
          gra[index] = new Graphic(singleRingPolygon, sfs); 

          gra[index].ZIndex = 3;
          gra[index].PolygonId = index;
          gra[index].type = "1";
          map.graphics.add(gra[index]);

     }
  }

......

map.graphics.on("mouse-down", function(evt){
      if(evt.which == 3) {

      polygonID = evt.graphic.PolygonId;
      polygonString = JSON.stringify(evt.graphic.geometry.rings);
      btype = evt.graphic.type;


....... 


}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
SiyabongaKubeka
Occasional Contributor

Hi @DanielBonan , may you please show me how did you enable you map to be able to allow the drawing of multiple polygons?

 

Regards

Siyabonga

0 Kudos