1 2// create global reference to worker performing source matching 3var matchingWorker = null; 4 5// spatial stats stuff 6function calcSpatialStats(features) { 7 var stats = {}; 8 var circumf = 0; 9 var vertices = 0; 10 var area = 0; 11 var segLengths = []; 12 for (feat of features) { 13 var geom = feat.getGeometry(); 14 circumf += ol.sphere.getLength(geom); 15 area += ol.sphere.getArea(geom); 16 if (geom.getType() == 'Polygon') { 17 // wrap so can treat at multipolygon 18 var polys = [geom.getCoordinates()]; 19 } else { 20 var polys = geom.getCoordinates(); 21 }; 22 for (poly of polys) { 23 for (ring of poly) { 24 vertices += ring.length; 25 }; 26 /* 27 // poly level aggregate stats 28 var ext = poly[0]; // polygon exterior 29 vertices += ext.length; 30 var line = turf.lineString(ext); 31 circumf += turf.length(line); 32 var polygon = turf.polygon([ext]); 33 area += turf.area(polygon); 34 // calc detailed segment lengths 35 //for (var i = 1; i < (ext.length-1); i++) { 36 // v1 = ext[i]; 37 // v2 = ext[i+1]; 38 // var len = turf.length(turf.lineString([v1,v2])); 39 // segLengths.push(len); 40 //}; 41 */ 42 }; 43 }; 44 // store final stats 45 stats.adminCount = features.length; 46 stats.area = area / 1000000.0; // convert from m2 to km2 47 circumf = circumf / 1000.0; // convert from m to km 48 stats.circumf = circumf; 49 stats.vertices = vertices; 50 stats.avgLineResolution = circumf / vertices * 1000; // ie avg vertext-to-vertex segment length ie resolution in meters 51 stats.avgLineDensity = vertices / circumf; // ie avg number of vertices per 1 km distance ie density (inverse of line resolution) 52 //segLengths.sort(function(a, b) {return a - b}); 53 //stats.medLineResolution = segLengths[Math.round(segLengths.length/2)] / 1000.0; // convert from m to km, even though supposed to be km 54 return stats; 55}; 56 57//----------------------------------- 58// this chunk defines boundary comparisons 59 60// helpers 61function geoj2turf(geoj) { 62 if (geoj.type == 'Polygon') { 63 geom = turf.polygon(geoj.coordinates) 64 } else if (geoj.type == 'MultiPolygon') { 65 geom = turf.multiPolygon(geoj.coordinates) 66 }; 67 return geom; 68}; 69 70// feature to feature similarity 71function robustTurfArea(geom) { 72 // WARNING: turf calculates wildly incorrect area estimates, see instead 'olArea' 73 // make sure isec rings are correctly sorted (otherwise will get negative area) 74 // see https://github.com/Turfjs/turf/issues/1482 75 // https://github.com/w8r/martinez/issues/91 76 var geomType = turf.getType(geom); 77 var coords = turf.getCoords(geom); 78 alert('area calc for '+geomType+' '+coords.length+' '+JSON.stringify(coords)); 79 if (geomType == 'Polygon') { 80 // treat as a multipolygon w one polygon 81 coords = [coords]; 82 }; 83 // reorganize the polygon rings to make sure it's correct 84 var allRings = []; 85 for (poly of coords) { 86 for (ring of poly) { 87 /*if (ring[0] != ring[ring.length-1]) { 88 ring.push(ring[ring.length-1]); 89 };*/ 90 allRings.push(ring); 91 }; 92 }; 93 var multiLines = turf.multiLineString(allRings); 94 var featureColl = turf.polygonize(multiLines); 95 // cumulate the area of each resulting polygon 96 var area = 0; 97 var pi = 0; 98 turf.featureEach(featureColl, function (currentFeature, featureIndex) { 99 area = area + turf.area(currentFeature); 100 pi++; 101 }); 102 alert('old '+turf.area(geom)+' new '+area); 103 104 /* 105 // calc area one ring at a time 106 area = 0; 107 let ip = 0; 108 for (poly of coords) { 109 let ir = 0; 110 var ringAreas = []; 111 for (ring of poly) { 112 var ringArea = turf.area(turf.polygon([ring])); 113 alert(ip+'-'+ir+', verts '+ring.length+', area '+ringArea); 114 //ringArea = Math.abs(ringArea); 115 //if (ir > 0) { 116 // ringArea = -ringArea; 117 //}; 118 ringAreas.push(ringArea); 119 //area = area + ringArea; 120 ir++; 121 }; 122 var extArea = Math.max.apply(Math, ringAreas); 123 var sumArea = ringAreas.reduce((a, b) => a + b, 0); 124 var holeArea = sumArea - extArea; 125 area = area + extArea - holeArea; 126 ip++; 127 }; 128 */ 129 130 return area; 131}; 132 133function olArea(geom) { 134 var feat = {'type':'Feature', 'properties':{}, 'geometry':{'type':turf.getType(geom), 'coordinates':turf.getCoords(geom)}}; 135 var olFeat = new ol.format.GeoJSON().readFeature(feat); 136 var area = ol.sphere.getArea(olFeat.getGeometry()); 137 //alert(turf.area(geom)+'-->'+area); 138 return area; 139}; 140 141function olPerimeter(geom) { 142 fdsfs; 143}; 144 145function similarity(feat1, feat2) { 146 try { 147 // create turf objects 148 //alert('creating turf geoms'); 149 geom1 = geoj2turf(turf.simplify(feat1.geometry, {tolerance:0.01})); 150 geom2 = geoj2turf(turf.simplify(feat2.geometry, {tolerance:0.01})); 151 152 // exit early if no overlap 153 /* 154 var [xmin1,ymin1,xmax1,ymax1] = turf.bbox(geom1); 155 var [xmin2,ymin2,xmax2,ymax2] = turf.bbox(geom2); 156 var boxoverlap = (xmin1 <= xmax2 & xmax1 >= xmin2 & ymin1 <= ymax2 & ymax1 >= ymin2) 157 if (!boxoverlap) { 158 return {'equality':0, 'within':0, 'contains':0} 159 }; 160 */ 161 162 // calc intersection 163 //alert('calc intersection'); 164 var isec = turf.intersect(geom1, geom2); 165 if (isec == null) { 166 // exit early if no intersection 167 return {'equality':0, 'within':0, 'contains':0} 168 }; 169 170 // calc union 171 //alert('calc union'); 172 var union = turf.union(geom1, geom2); 173 174 // calc metrics 175 //alert('calc areas'); 176 var geom1Area = turf.convertArea(olArea(geom1),'meters','kilometers'); 177 var geom2Area = turf.convertArea(olArea(geom2),'meters','kilometers'); 178 var unionArea = turf.convertArea(olArea(union), 'meters', 'kilometers'); 179 var isecArea = turf.convertArea(olArea(isec), 'meters', 'kilometers'); 180 var areas = {'geom1Area':geom1Area, 'geom2Area':geom2Area, 'unionArea':unionArea, 'isecArea':isecArea}
180; 181 //alert(JSON.stringify(areas)); 182 183 var results = {}; 184 results.equality = isecArea / unionArea; 185 results.within = isecArea / geom1Area; 186 results.contains = isecArea / geom2Area; 187 } catch (error) { 188 console.warn('similarity could not be calculated: ' + error); 189 results = {equality:NaN, within:NaN, contains:NaN}; 190 }; 191 return results; 192}; 193 194function calcSpatialRelations(feat, features) { 195 //feat = feat.getGeometry().simplify(0.01); 196 var geojWriter = new ol.format.GeoJSON(); 197 var geoj1 = geojWriter.writeFeatureObject(feat); 198 var matches = []; 199 for (feat2 of features) { 200 if (!ol.extent.intersects(feat.getGeometry().getExtent(), feat2.getGeometry().getExtent())) { 201 continue; 202 }; 203 //feat2 = feat2.getGeometry().simplify(0.01); 204 geoj2 = geojWriter.writeFeatureObject(feat2); 205 simil = similarity(geoj1, geoj2); 206 if (simil.equality > 0.0) { 207 matches.push([feat2,simil]); 208 }; 209 i++; 210 }; 211 return matches; 212}; 213 214function calcAllSpatialRelations(data1, data2, onSuccess, onProgress=null) { 215 // calc relations from 1 to 2 216 // calculate everything in background and receive results at end 217 // to avoid locking up the entire gui 218 219 // terminate any previous worker 220 if (matchingWorker !== null) { 221 matchingWorker.terminate(); 222 }; 223 224 // create worker 225 matchingWorker = new Worker('assets/js/internalMatcherWorker.js'); 226 console.log(matchingWorker); 227 228 // define how to process messages 229 function processResults(results) { 230 console.log('received results:'); 231 console.log(results); 232 onSuccess(results); 233 }; 234 function processMessage(event) { 235 let [status,data] = event.data; 236 if (status == 'processing') { 237 let [i,total] = data; 238 onProgress(i, total); 239 } else if (status == 'finished') { 240 let results = data; 241 processResults(results); 242 }; 243 }; 244 matchingWorker.onmessage = processMessage; 245 246 // tell worker to start processing 247 matchingWorker.postMessage([data1, data2]); 248}; 249 250function sortSpatialRelations(matches, sort_by, thresh, reverse=true) { 251 // sort 252 function sortFunc(a, b) { 253 if (reverse == false) { 254 var trueVal = 1; 255 } else { 256 var trueVal = -1; 257 }; 258 if (a[1][sort_by] < b[1][sort_by]) { 259 // a is less than b by some ordering criterion 260 return -trueVal; 261 }; 262 if (a[1][sort_by] > b[1][sort_by]) { 263 // a is greater than b by the ordering criterion 264 return trueVal; 265 }; 266 // a must be equal to b 267 return 0; 268 }; 269 matches.sort(sortFunc); 270 271 // filter by threshold 272 newMatches = []; 273 for (m of matches) { 274 if (m[1][sort_by] >= thresh) { 275 newMatches.push(m); 276 }; 277 }; 278 279 return newMatches; 280}; 281 282function calcBestMatches(matches) { 283 // this should output a simpler match list 284 // with one row for every feat1 285 // in the format feat,bestmatchfeat,stats 286 // where multiple feats can't match to the same feat 287 288 // helper to find features that match another 289 function findFeaturesThatMatch(matchID) { 290 result = []; 291 for (x of matches) { 292 var [feature,related] = x; 293 related = sortSpatialRelations(related, 'equality', 0.01); 294 if (related.length==0) {continue}; 295 for (y of related) { 296 var [matchFeat,stats] = y; 297 if (matchFeat.id == matchID) { 298 result.push([feature,stats]); 299 }; 300 }; 301 }; 302 return result; 303 }; 304 305 // create best match list 306 var finalMatches = []; 307 for (x of matches) { 308 var [feature,related] = x; 309 // match with highest equality 310 related = sortSpatialRelations(related, 'equality', 0.01); 311 if (related.length==0) { 312 finalMatches.push([feature,null,null]); 313 continue; 314 }; 315 [bestMatchFeat,bestStats] = related[0]; 316 // make sure this match is the highest among all others 317 // ie only the feature with the best match to another is allowed 318 // ie multiple feats can't match another 319 var othersThatMatch = findFeaturesThatMatch(bestMatchFeat.id); 320 othersThatMatch = sortSpatialRelations(othersThatMatch, 'equality', 0.01); 321 [bestOtherThatMatches,bestOtherThatMatchesStats] = othersThatMatch[0]; 322 if (feature.id == bestOtherThatMatches.id) { 323 finalMatches.push([feature,bestMatchFeat,bestStats]); 324 } else { 325 finalMatches.push([feature,null,null]); 326 }; 327 }; 328 329 // return 330 return finalMatches; 331}; 332 333
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.