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:
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)
I have a javascript function to_polygon(location) that will take location from ${ply} or ${pnt} and return a polygon:
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.
Any ideas How I could solve this issue (other than convincing the user that he shouldn't want this flexibility in drawing geometries😀?)
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
}