Use pulldata javascript to create polygon from point or line

119
1
2 weeks ago
Labels (1)
MarcHoogerwerf_EsriNL
New Contributor

I'm trying to use a pulldata javascript function in S123Connect to calculate a polygon from a point or line geometry provided by the user.

I have a survey with 1 geom_type field (select_one geom_types with values "polygon","polyline","point") and 3 geometry fields:

  • pol -> geoshape, will be stored in the feature service, with a calculation 

 

if(selected(${geom_type},'polyline'),
pulldata("@javascript","functions.js","to_polygon",string(${ply}),
if(selected(${geom_type},'point'),pulldata("@javascript","functions.js","to_polygon",string(${pnt}),
null)​

 

  • ply -> geotrace, will not be stored in the feature service (esriFieldType null), relevant if:  selected(${geom_type},'polyline')
  • pnt -> geopoint, will not be stored in the feature service (esriFieldType null), relevant if selected(${geom_type},'point')

I have a javascript function to_polygon(location) that will take location from ${ply} or ${pnt} and return a polygon:

  • from ${pnt}: project the point using ArcGIS Online geometry service to a point in Web Mercator, then use an offset to calculate the polygon as a square, reproject the square to EPSG: 4326 and return coordinates as a string
  • from ${ply}: project the polyline using ArcGIS Online geometry services to a polyline in Web Mercator, then calculate a polyline at an offset, add coordinates from both lines (coordinates from the offset line in reverse order) and close with starting point from polyline to create the polygon. Reproject the polygon back to EPSG:4326, return coordinate list as a string

Before implementing the whole function I created a small prototype function to test if the mechanism would actually work with a simple point, not yet implementing the projection stuff:

 

function to_polygon(location) {
	if (location ==="") {return "Location is empty"}

	var geom = "";
    if (location.includes("rings")) {
    	geom = jSON.stringify(location);
    } else if (location.includes("paths")){
		geom = jSON.stringify(location);
    } else {	
		var pt_0 = {"x":location.split(" ")[0],"y":location.split(" ")[1],"spatialReference": {"wkid": 4326}};
		geom = geom + (parseFloat(pt_0["x"]) - 0.00001).toString() + " " + (parseFloat(pt_0["y"]) - 0.00001).toString() + ";";
		geom = geom + (parseFloat(pt_0["x"]) + 0.00001).toString() + " " + (parseFloat(pt_0["y"]) - 0.00001).toString() + ";";
		geom = geom + (parseFloat(pt_0["x"]) + 0.00001).toString() + " " + (parseFloat(pt_0["y"]) + 0.00001).toString() + ";";
		geom = geom + (parseFloat(pt_0["x"]) - 0.00001).toString() + " " + (parseFloat(pt_0["y"]) + 0.00001).toString() + ";";
		geom = geom + (parseFloat(pt_0["x"]) - 0.00001).toString() + " " + (parseFloat(pt_0["y"]) - 0.00001).toString() + ";";
    }
	return "\""+ geom + "\"";
}

 

 

If I test my function and store the function result in a text field, it works, see screenshot below. However, if I try to refer to the outcome in a calculation for the geoshape field, I receive the following error: ODK Validate Errors: Sometthing broke the parser. See above for a hint. Error evaluating field 'pol': For input string: "@javascript" caused by: java,lang.NumberFormatException: For input string: "@javascript" ... 10 more Result: Invalid.

If I copy the function result 

 

"51.87814892676192 5.15626525895422;51.87816892676192 5.15626525895422;51.87816892676192 5.156285258954219;51.87814892676192 5.156285258954219;51.87814892676192 5.15626525895422;"

 

in the calculation for pol, I get the expected polygon for the pol.

So the problem doesn't seem to be in the function result itself but in the parsing of the function and calculation expression. 

2024-04-23_081750.png

 

Any ideas How I could solve this issue (other than convincing the user that he shouldn't want this flexibility in drawing geometries😀?)

1 Reply
MarcHoogerwerf_EsriNL
New Contributor

Short update.

I managed to get this working. The Validation error was caused by a circular reference.

In case anyone is interested to do a similar thing, please find my Excel attached.

Here's the calculation of the resulting polygon:

if(selected(${geometrie_type},'vlak') and string-length(${vlak})>0,pulldata("@javascript", "functions.js", "to_polygon","https://tasks.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer/offset",string(${vlak}),0,${ago_token},true),
if(selected(${geometrie_type},'lijn') and string-length(${lijn})>0,pulldata("@javascript", "functions.js", "to_polygon","https://tasks.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer/offset",string(${lijn}),1,${ago_token},true),
if(selected(${geometrie_type},'punt') and string-length(${punt})>0,pulldata("@javascript", "functions.js", "to_polygon","https://tasks.arcgisonline.com/arcgis/rest/services/Geometry/GeometryServer/offset",string(${punt}),1,${ago_token},true),"")))

 

The javascript function uses the ArcGIS Online Geometry Service to project coordinates (between WGS84 and RD_New in my case, check and change the hardcoded transformation parameter if you need to project to other coordinate systems), calculate the offsets and assemble the resulting polygon. 

I still see a couple of issues, first among them that the current implementation is quite slow. There's also something weird thing going on with the polygon created from the line, it seems to be cut somehow. And probably you want to offset the polyline to both left and right sides instead of the current right hand offset and create the polygon from those. But anyway, some proof of concept is there. I welcome any suggestion on how to speed this thing up.

I've used these functions to get polygons from points and lines with an offset. The code, as always, is a bit brittle.

function to_polygon(gs_url,location,offset,token,debugmode) {
	
	if (location === undefined) {return "Location is empty"}

	var geom = "";
    if (location.includes("rings")) {
		var coordslist = JSON.parse(location).rings[0];
		coordslist.forEach(coord => {
			geom += coord.slice(0,2).reverse().join(" ") + ";"
        });
    } else if (location.includes("paths")){
		geom = polyline_to_polygon(gs_url,location,offset,token,debugmode);
    } else {

		geom = project(gs_url.replace("offset","project"),location,4326,28992,token,debugmode).geometries[0];

		var pt0 = [parseFloat(geom["x"]) - offset/2, parseFloat(geom["y"]) - offset/2];
		var pt1 = [parseFloat(geom["x"]) + offset/2, parseFloat(geom["y"]) - offset/2];
		var pt2 = [parseFloat(geom["x"]) + offset/2, parseFloat(geom["y"]) + offset/2];
		var pt3 = [parseFloat(geom["x"]) - offset/2, parseFloat(geom["y"]) + offset/2];
		var pt4 = [parseFloat(geom["x"]) - offset/2, parseFloat(geom["y"]) - offset/2];
		
		var pol = {"spatialReference":{"wkid":28992},
              "rings": [[pt0,pt1,pt2,pt3,pt4]]
             };

		geom = project(gs_url.replace("offset","project"),JSON.stringify(pol),28992,4326,token,debugmode)
		var coordslist = geom.geometries[0].rings[0];
   		var coords = "";
   		coordslist.forEach(coord => {
			coords += coord.join(" ") + ";"
   		});
   		geom = coords
    }
	return "\""+ geom + "\"";
}

function project(gs_url,geom,inSR,outSR,token,debugmode) {
	var geoms;
    if (geom.includes("rings")){
	    geoms = {"geometryType":"esriGeometryPolygon",
                 "geometries": [{"rings":JSON.parse(geom)["rings"]}]
                };
	}
	else if (geom.includes("paths")){
	    geoms = {"geometryType":"esriGeometryPolyline",
                 "geometries": [{"paths":JSON.parse(geom)["paths"]}]
                };

    } else {
    	geoms = {"geometryType":"esriGeometryPoint",
                 "geometries": [{"x":geom.split(" ")[0],"y":geom.split(" ")[1]}]
                }
    } 

	var forward = false
	if (inSR === 4326) {forward = true}

    var url = gs_url;
	url += "?f=json&";
	url += "geometries=" + JSON.stringify(geoms) + "&";
	url += "transformation=108042&"
	url += "transforward=" + forward + "&"
    url += "inSR=" + inSR + "&outSR=" + outSR;
    url = encodeURI(url) 

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET",url,false);
	xmlhttp.send();
 	if (xmlhttp.status!==200){
        return (debugmode? xmlhttp.status:"");
    } else {
		var responseJSON=JSON.parse(xmlhttp.responseText);
		console.log(JSON.stringify(responseJSON));
		if (responseJSON.error){
            return (debugmode? JSON.stringify(responseJSON.error):"");
        } else {
			if (responseJSON.geometries){
                return responseJSON;
            }
            else {
                return (debugmode? "No Features Found":"");
            }
		}
	} 
}



function polyline_to_polygon(gs_url,geom,offset,token,debugmode){
	// Project polyline to RD_New
    geom = project(gs_url.replace("offset","project"),geom,4326,28992,token,debugmode)

    var geoms = {"geometryType":"esriGeometryPolyline",
                 "geometries": [{"paths":geom.geometries[0]["paths"]}]
                };
	var org_paths = geom.geometries[0]["paths"][0]
	var start_coords = org_paths[0]

	// Create an offset polyline and assemble a polyon from the original polyline
    // and the offset polyline. 
    // Close the polygon by adding the startpoint of the original polyline
	var url = gs_url;
		url += "?f=json&";
		url += "geometries=" + JSON.stringify(geoms) + "&";
		url += "offsetDistance=" + offset + "&"
		url += "offsetUnit=	9001&"
		url += "offsetHow=esriGeometryOffsetMitered&"
		url += "simplifyResult=true&"
		url += "sr=28992";
		url = encodeURI(url);

	var rings;
	var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("GET",url,false);
		xmlhttp.send();
		if (xmlhttp.status!==200){
			return (debugmode? xmlhttp.status:"");
		} else {
			var responseJSON=JSON.parse(xmlhttp.responseText);
			console.log(JSON.stringify(responseJSON));
			if (responseJSON.error){
				return (debugmode? JSON.stringify(responseJSON.error):"");
			} else {
				if (responseJSON.geometries){
					var offset_paths = responseJSON.geometries[0].paths[0]
				}
				else {
					return (debugmode? "No Features Found":"");
				}
			}
		}  


	rings = org_paths.concat(offset_paths.reverse());
	rings.push(start_coords);

    var pol = {"spatialReference":{"wkid":28992},
              "rings": [rings]
              };
	// Project the polygon back to WGS84
    geom = project(gs_url.replace("offset","project"),JSON.stringify(pol),28992,4326,token,debugmode);

	// Assemble the list of coordinates
    var coordslist = geom.geometries[0].rings[0];
    var coords = "";
    coordslist.forEach(coord => {
    	coords += coord.reverse().join(" ") + ";"
    });
    return coords
}

 

0 Kudos