PageSourceSearch

https://ifarm.nl/maps/arvid/

html ifarm.nl collected 2026-10-02 07:38:03 UTC 65,724 bytes, 1,226 lines download raw bytes

1<html>
2  <head>
3    <link rel="stylesheet" href="ol.css" type="text/css">
4    
4<script type="text/javascript" src="ol.js"></script>
4
5    
5<script type="text/javascript" src="rhill-voronoi-core.js"></script>
5
6    
6<script type="text/javascript" src="sorttable.js"></script>
6
7    
7<script type="text/javascript" src="zip.js"></script>
7
8    
8<script type="text/javascript" src="kloeke.js"></script>
8
9    <!-- sorting only works one way; -->
10  </head>
11  <body style="background-color:#eeeeee;">
12    <p>
13      <strong>ARVID</strong>
14      &nbsp; View:
15      <select id="mode" onchange="view(this.value)">
16        <option value="map" selected>map</option>
17        <option value="table">table</option>
18      </select>
19      &nbsp; <button onclick="if (document.getElementById('mode').value == 'table') { saveCSV(); } else { saveKML(); }">Save File</button>
20      &nbsp; <select id="example" onchange="examples(this.value)">
21        <option value="0" selected>Examples</option>
22        <option value="0">Default</option>
23        <option value="1">New York</option>
24        <option value="2">Erik's data 1</option>
25        <option value="3">Erik's data 2</option>
26        <option value="4">Nina's data 1</option>
27        <option value="5">Basque 1</option>
28        <option value="8">Basque 2</option>
29        <option value="10">Basque 3</option>
30        <option value="11">Basque 4</option>
31        <option value="12">Basque 6</option>
32        <option value="13">Basque 8</option>
33        <option value="6">Basque 15</option>
34        <option value="7">Basque 16</option>
35        <option value="9">Basque 24</option>
36        <option value="14">Spruit</option>
37      </select>
38      &nbsp; Load File:
39      <input type="file" id="files" name="files[]" multiple /><br/>
40      <font id="mapbuttons">
41        &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp;&nbsp; Cell size:
42        <input id="maxDistance" size="5" onchange="window.maxDistance=Number(this.value); cleanError(); viewMap();" />
43        &nbsp; Opacity:
44        <input id="opacity" size="3" onchange="window.opacity=Number(this.value); cleanError(); viewMap();" />
45        &nbsp; Background map:
46        <select id="backgroundMap" onchange="viewMap()">
47          <option value="off" selected>off</option>
48          <option value="on">on</option>
49        </select>
50      </font>
51      &nbsp; <a href="help.html">Help</a>
52    </p>
53    <div id="map" class="map" style="width:700; height:525; background-color:white;"></div>
54    <div id="legend" class="legend"></div>
55    <div>
56      Combine:
57      <input id="combine" size="5" onchange="window.combine=Number(this.value); cleanError(); view(document.getElementById('mode').value);" />
58      <font id="mapbuttons2">
59        &nbsp; Transition points:
60        <input id="transition" size="5" onchange="window.transition=Number(this.value); cleanError(); viewMap();" />
61        &nbsp; Distance:
62        <input id="distance" size="5" onchange="window.distance=Number(this.value); cleanError(); viewMap();" />
63        &nbsp; Palette:
64        <input id="palette" size="5" onchange="window.palette=Number(this.value); cleanError(); viewMap();" />
65        <a onclick="window.palette=window.palette-1; while (window.palette<0) {window.palette += 48; } document.getElementById('palette').value = window.palette; cleanError(); viewMap();" style="color:blue;" />[-1]</a>
66        <a onclick="window.palette=window.palette+1; while (window.palette>=48) {window.palette -= 48; }document.getElementById('palette').value = window.palette; cleanError(); viewMap();" style="color:blue;" />[+1]</a>
67      </font>
68    </div>
69    <div id="comment" class="comment"></div>
70    <div id="error" class="error"></div>
71    
71<script type="text/javascript">
72      // FUNCTIONS:
73      // view(mode,sitesFile) 
74      //   viewTable() 
75      //   viewMap() 
76      // handleFileSelect(evt) 
77      //   processData(sitesFile) 
78      //     processCSV(xmlDoc) 
79      //     processKML(xmlDoc) 
80      // saveKML() 
81      // saveCSV() 
82      // clipEdges(sitex,sitey,edges,maxDistance) 
83      //   findIntersectionPoints(sitex,sitey,maxDistance,x0,y0,x1,y1) 
84      //   addCurve(nodes,sitex,sitey,x0,y0,x1,y1,nbrOfPoints) 
85      // sortEdges(edges) 
86      // error(string) 
87      // makeDateString(date) 
88      // color2hex(string) 
89      //
90      // 20150521 printing maps: http://trac.osgeo.org/openlayers/wiki/TileStitchingPrinting
91      
92      // default file with data points
93      var mainSitesFile = "default.kml";
94      // main data structure
95      var datapoints = [];
96      // list of modified data values
97      var updates = [];
98      var fileName = "";
99      var opacity = -1;
100      var maxDistance = -1;
101      var combine = 1;
102      var transition = 0;
103      var distance = 0;
104      var palette = 0;
105      var numericalData = false;
106      document.getElementById("files").value = "";
107      document.getElementById("palette").value = "0";
108      document.getElementById("example").value = "0";
109      // define 14 colors with names and color map pointing to them
110      var colorNames = [ "white","red","green","blue","yellow","magenta","cyan","l red","l green","l blue","l yellow","l magenta","l cyan","black" ];
111      var colorMap = [ 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ];
112      // check cgi variables
113      checkCGI();
114      document.getElementById("combine").value = combine;
115      document.getElementById("transition").value = transition;
116      document.getElementById("distance").value = 0;
117      // run main function
118      processData(mainSitesFile);
119      if (typeof(document.getElementById("mode").value) != "undefined" &&
120          document.getElementById("mode").value == "table") { viewTable(); } 
121      else { viewMap(); }
122      
123      function view(mode) {
124        cleanError();
125        if (mode == "table") { viewTable(); }
126        else { viewMap(); }
127      }
128      
129      // process uploaded file
130      // source: http://www.html5rocks.com/en/tutorials/file/dndfiles/
131      function handleFileSelect(evt) {
132        cleanError();
133        var files = evt.target.files; // FileList object
134        // clear earlier updates
135        updates = [];
136        opacity = -1;
137        maxDistance = -1;
138        document.getElementById("opacity").value = "";
139        document.getElementById("maxDistance").value = "";
140        // Loop through the FileList
141        for (var i = 0, f; f = files[i]; i++) {
142          if (/earth.kmz/.test(f.type) || /\.kmz$/.test(f.name)) {
143            fileName = f.name;
144            fileName = fileName.replace(/\.kmz$/,".kml");
145            // source: http://gildas-lormeau.github.io/zip.js/core-api.html
146            zip.createReader(new zip.BlobReader(f), function(zipReader) {
147              // get all entries from the zip
148              zipReader.getEntries(function(entries) {
149                if (entries.length) {
150                  for (var i=0;i<entries.length;i++) {
151                    if (entries[i].filename == "doc.kml") {
152                      // get first entry content as text
153                      entries[i].getData(new zip.TextWriter(), function(text) {
154                        // text contains the entry data as a String
155                        processData(text);
156                        if (typeof(document.getElementById("mode").value) != "undefined" &&
157                            document.getElementById("mode").value == "table") { viewTable(); } 
158                        else { viewMap(); }
159                        // close the zip reader
160                        zipReader.close(function() {
161                          // onclose callback
162                        });
163                      }, function(current, total) {
164                        // onprogress callback
165                      });
166                    }
167                  }
168                }
169              });
170            }, function(error) {
171              // onerror callback
172              error("Error reading file "+f.name+" "+error);
173            });
174          } else { // assume input file is a text file
175            fileName = f.name;
176            var reader = new FileReader();
177            // Closure to capture the file information.
178            reader.onload = (function(theFile) {
179              return function(e) {
180                var text = e.target.result;
181                processData(text);
182                if (typeof(document.getElementById("mode").value) != "undefined" &&
183                  document.getElementById("mode").value == "table") { viewTable(); }
184                else { viewMap(); }
185              };
186            })(f);
187            // Read in the image file as a data URL.
188            reader.readAsText(f);
189          }
190        }
191      }
192      document.getElementById('files').addEventListener('change', handleFileSelect, false);
193      
194      // store data from file in main data structure
195      function processData(sitesFile) {
196        mainSitesFile = sitesFile;
197        datapoints = [];
198        var xmlDoc = "";
199        // a kml document is stored in string sitesFile
200        if (/^</.test(sitesFile)) {
201          var wrapper= document.createElement('div');
202          wrapper.innerHTML = sitesFile;
203          xmlDoc = wrapper.getElementsByTagName("kml")[0];
204          processKML(xmlDoc);
205        // a csv document is stored in string sitesFile
206        } else if (/,/.test(sitesFile) && sitesFile.length > 50) {
207          processCSV(sitesFile);
208        // load file from server, assume the format is kml
209        } else {
210          // load the data points
211          if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
212            xmlhttp = new XMLHttpRequest();
213          } else { // code for IE6, IE5
214            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
215          }
216          xmlhttp.open("GET",sitesFile,false);
217          xmlhttp.send();
218          xmlDoc = xmlhttp.responseXML;
219          processKML(xmlDoc);
220        }
221      }
222      
223      function processCSV(xmlDoc) {
224        // process the data points
225        var lines = xmlDoc.split("\n");
226        var columns = [];
227        var seen = [];
228        numericalData = true;
229        var warnings = 0;
230        for (var l=0;l<lines.length;l++) {
231          var segments = lines[l].split("\r");
232          for (var s=0;s<segments.length;s++) {
233            lines[l] = segments[s];
234            if (typeof(lines[l]) !== "undefined" && lines[l] != "") {
235              var fields = lines[l].split(",");
236              var f = 0;
237              while (f < fields.length-1) {
238                var field = fields[f];
239                if ((/^"/.field && ! /"$/.field) ||
240                    (/^'/.field && ! /'$/.field)) {
241                  fields[f] += ","+fields[f+1];
242                  fields.splice(f+1,1);
243                } else { f++; }
244              }
245              // proces column names
246              if (l == 0 && s == 0) {
247                for (f=0;f<fields.length;f++) {
248                  var field = fields[f];
249                  field = field.replace(/^['"]/,"");
250                  field = field.replace(/['"]$/,"");
251                  if (/^longitude$/i.test(field) || /^x$/i.test(field)) { columns[f] = "longitude"; }
252                  else if (/^latitude$/i.test(field) || /^y$/i.test(field)) { columns[f] = "latitude"; }
253                  else if (/^name$/i.test(field)) { columns[f] = "name"; }
254                  else if (/^value$/i.test(field)) { columns[f] = "value"; }
255                  else if (/^kloeke$/i.test(field)) { columns[f] = "kloeke"; }
256                  else if (/^numbers$/i.test(field)) { columns[f] = "numbers"; }
257                  else if (/^distance$/i.test(field)) { columns[f] = "distance"; }
258                  // else { error("Unknown column name in csv file! "+fields[f]+"\n"); }
259                }
260              // process data lines
261              } else {
262                var longitude = ""; var latitude = ""; var name = ""; var value = ""; var kloeke = "";
263                var numbers = ""; var distance = "";
264                for (f=0;f<fields.length;f++) {
265                  fields[f] = fields[f].replace(/^['"]/,"");
266                  fields[f] = fields[f].replace(/['"]$/,"");
267                  fields[f] = fields[f].replace(/\s*$/,"");
268                  if (columns[f] == "longitude") { longitude = f
268ields[f]; }
269                  else if (columns[f] == "latitude") { latitude = fields[f]; }
270                  else if (columns[f] == "name") { name = fields[f]; }
271                  else if (columns[f] == "value") { value = fields[f]; }
272                  else if (columns[f] == "numbers") { numbers = fields[f]; }
273                  else if (columns[f] == "distance") { distance = fields[f]; }
274                  else if (columns[f] == "kloeke") { 
275                    kloeke = fields[f];
276                    if (typeof(kloekePoints[kloeke]) === "undefined") { error("undefined kloeke value "+kloeke+"!"); warnings++; }
277                    else {
278                      if (typeof(longitude) === "undefined" || longitude == "") { longitude = kloekePoints[kloeke].longitude; }
279                      if (typeof(latitude) === "undefined" || latitude == "") { latitude = kloekePoints[kloeke].latitude; }
280                      if (typeof(name) === "undefined" || name == "") { 
281                        if (typeof(kloekePoints[kloeke].city) !== "undefined") { name = kloekePoints[kloeke].city+" ("+kloeke+")"; }
282                        else { name = ""; }
283                        if (typeof(kloekePoints[kloeke].province) !== "undefined") { name += ", "+kloekePoints[kloeke].province; }
284                        if (typeof(kloekePoints[kloeke].country) !== "undefined") { name += ", "+kloekePoints[kloeke].country; }
285                      }
286                    }
287                  }
288                }
289                if (typeof(longitude) !== "undefined" && typeof(latitude) != "undefined" && longitude != "" && latitude != "") {
290                  // check if this is a new data point
291                  var seenKey = longitude+" "+latitude;
292                  if (typeof(seen[seenKey]) === "undefined") {
293                    datapoints.push({"longitude":longitude,"latitude":latitude,"value":value,"numbers":numbers,"distance":distance,"name":name});
294                    seen[seenKey] = datapoints.length-1;
295                    if (value != "" && ! /^-?\d+(\.\d+)?(#-?\d+(\.\d+)?)*$/.test(value)) { numericalData = false; }
296                  } else {
297                    // existing data point: add new value to existing values
298                    //var values = datapoints[seen[seenKey]].value.split("\n");
299                    //values.push(value);
300                    //values.sort();
301                    //datapoints[seen[seenKey]].value = values.join("\n");
302                    //numericalData = false;
303                    error("duplicate value for "+seenKey+"!");
304                  }
305                }
306              }
307            }
308          }
309        }
310        if (warnings > 1) { error(warnings+" errors"); }
311        if (numericalData) {
312          opacity = 1.0;
313          document.getElementById("opacity").value = opacity;
314        }
315      }
316      
317      function processKML(xmlDoc) {
318        // process the data points
319        var points = xmlDoc.getElementsByTagName("Placemark");
320        numericalData = true;
321        for (var p=0;p<points.length;p++) {
322          var name = "";
323          if (typeof(points[p].getElementsByTagName("name")[0]) != "undefined") {
324            name = points[p].getElementsByTagName("name")[0].childNodes[0].nodeValue;
325          }
326          try {
327            var value = "";
328            if (typeof(points[p].getElementsByTagName("Data")[0].getElementsByTagName("value")[0].childNodes[0]) !== "undefined") {
329              value = points[p].getElementsByTagName("Data")[0].getElementsByTagName("value")[0].childNodes[0].nodeValue;
330            }
331            var numbers = "";
332            if (typeof(points[p].getElementsByTagName("Data")[1]) !== "undefined" &&
333                typeof(points[p].getElementsByTagName("Data")[1].getElementsByTagName("value")[0].childNodes[0]) !== "undefined") {
334              numbers = points[p].getElementsByTagName("Data")[1].getElementsByTagName("value")[0].childNodes[0].nodeValue;
335            }
336            var distance = "";
337            if (typeof(points[p].getElementsByTagName("Data")[2]) !== "undefined" &&
338                typeof(points[p].getElementsByTagName("Data")[2].getElementsByTagName("value")[0].childNodes[0]) !== "undefined") {
339              distance = points[p].getElementsByTagName("Data")[2].getElementsByTagName("value")[0].childNodes[0].nodeValue;
340            }
341            if (typeof(points[p].getElementsByTagName("Point")[0]) !== "undefined") {
342              var coordinates = points[p].getElementsByTagName("Point")[0].getElementsByTagName("coordinates")[0].childNodes[0].nodeValue.split(/,/);
343              datapoints.push({"longitude":coordinates[0],"latitude":coordinates[1],"value":value,"numbers":numbers,"distance":distance,"name":name});
344              if (value != "" && ! /^-?\d+(\.\d+)?(#-?\d+(\.\d+)?)*$/.test(value)) { numericalData = false; }
345            }
346          } catch(err) {
347             error('Fout! p="'+p+'" name="'+name+' '+err.message);
348          }
349        }
350        // process the style information; first reset style values
351        maxDistance = -1;
352        opacity = -1;
353        if (typeof(xmlDoc.getElementsByTagName("Style")[0]) !== "undefined") {
354           var styleNodes = xmlDoc.getElementsByTagName("Style")[0].childNodes;
355           if (styleNodes.length == 1 && /</.test(styleNodes[0].nodeValue)) {
356             // problem: style node contents was not parsed
357             var tmpNode = document.createElement("div");
358             tmpNode.innerHTML = styleNodes[0].nodeValue;
359             styleNodes = tmpNode.childNodes;
360           }
361           for (var i=0;i<styleNodes.length;i++) {
362             if (typeof(styleNodes[i].id) !== "undefined" && styleNodes[i].id == "arvid") {
363               var color = styleNodes[i].getElementsByTagName("color")[0].childNodes[0].nodeValue;
364               var scale = styleNodes[i].getElementsByTagName("scale")[0].childNodes[0].nodeValue;
365               maxDistance = Number(scale);
366               color = color.replace(/......$/,"");
367               opacity = parseInt(color,16)/255;
368             }
369           }
370        }
371        if (numericalData) {
372          opacity = 1.0;
373          document.getElementById("opacity").value = opacity;
374        }
375        // adjust html form values
376        if (maxDistance >= 0) { document.getElementById("maxDistance").value = maxDistance; }
377        else { document.getElementById("maxDistance").value = ""; }
378        if (opacity >= 0) { document.getElementById("opacity").value = opacity; }
379        else { document.getElementById("opacity").value = ""; }
380      }
381      
382      // save kml format
383      function saveKML () {
384        // take care of combined values
385        var values = [];
386        var combinedNames = [];
387        var combinedValues = [];
388        if (combine <= 1) {
389          for (var d=0;d<datapoints.length;d++) {
390            values[d] = datapoints[d].value;
391            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }
392          }
393        } else if (! numericalData) {
394          error("only numerical data can be combined!\n");
395          for (var d=0;d<datapoints.length;d++) {
396            values[d] = datapoints[d].value;
397            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }
398          }
399        } else {
400          var combinations = combineValues();
401          for (var d=0;d<datapoints.length;d++) {
402            values[d] = combinations[d].value;
403            combinedValues[d] = combinations[d].combinedValues;
404            combinedNames[d] = combinations[d].combinedNames;
405          }
406        }
407        // array values contains the values; combinedValues.length signals combination
408        var opacityHX = (256*opacity).toString(16);
409        opacityHX = opacityHX.replace(/\..*$/,"");
410        var date = new Date();
411        var data = '<?xml version="1.0" encoding="UTF-8"?>\n';
412        data += '<kml xmlns="http://www.opengis.net/kml/2.2">\n  <Document>\n    <name>Saved from Arvid at '+date+'</name>\n    <Style>\n      <IconStyle id="arvid">\n        <color>'+opacityHX+'ffffff</color>\n        <scale>'+maxDistance+'</scale>\n      </IconStyle>\n    </Style>\n';
413        for (var d=0;d<datapoints.length;d++) {
414          var value = values[d];
415          data += "    <Placemark>\n      <name>"+datapoints[d].name+"</name>\n      <Point><coordinates>"+datapoints[d].longitude+","+datapoints[d].latitude+",0</coordinates></Point>\n      <ExtendedData><Data name=\"value\"><value>"+value+"</value></Data>";
416          if (combinedValues.length > 0) { data += "<Data name=\"CombinedNames\"><value>"+combinedNames[d]+"</value></Data><Data name=\"CombinedValues\"><value>"+combinedValues[d]+"</value></Data>"; }
417          data += "</ExtendedData>\n    </Placemark>\n";
418        }
419        data += "  </Document>\n</kml>\n";
420        // source: http://stackoverflow.com/questions/283956/is-there-any-way-to-specify-a-suggested-filename-when-using-data-uri
421        var tmplink = document.createElement('a');
422        if (typeof tmplink.download === 'string') {
423          tmplink.href = "data:text/kml;charset=utf-8,"+encodeURIComponent(data);
424          tmplink.download = "arvid-"+makeDateString(date)+".kml";
425          document.body.appendChild(tmplink);
426          tmplink.click();
427          document.body.removeChild(tmplink);
428        } else {
429          window.location = "data:text/kml;charset=utf-8,"+encodeURIComponent(data);
430        }
431      }
432      
433      // save csv format
434      function saveCSV () {
435        // take care of combined values
436        var values = [];
437        var combinedNames = [];
438        var combinedValues = [];
439        if (combine <= 1) {
440          for (var d=0;d<datapoints.length;d++) {
441            values[d] = datapoints[d].value;
442            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }
443          }
444        } else if (! numericalData) {
445          error("only numerical data can be combined!\n");
446          for (var d=0;d<datapoints.length;d++) {
447            values[d] = datapoints[d].value;
448            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }
449          }
450        } else {
451          var combinations = combineValues();
452          for (var d=0;d<datapoints.length;d++) {
453            values[d] = combinations[d].value;
454            combinedValues[d] = combinations[d].combinedValues;
455            combinedNames[d] = combinations[d].combinedNames;
456          }
457        }
458        // array values contains the values; combinedValues.length signals combination
459        var date = new Date();
460        var data = 'longitude,latitude,value,name';
461        if (combinedValues.length > 0) { data += ',"combined names","combined values"'; }
462        data += '\n';
463        for (var d=0;d<datapoints.length;d++) {
464          var value = values[d];
465          data += datapoints[d].longitude+','+datapoints[d].latitude+',"'+value+'","'+datapoints[d].name+'"';
466          if (combinedValues.length > 0) { data += ',"'+combinedNames[d]+'","'+combinedValues[d]+'"'; }
467          data += "\n";
468        }
469        // source: http://stackoverflow.com/questions/283956/is-there-any-way-to-specify-a-suggested-filename-when-using-data-uri
470        var tmplink = document.createElement('a');
471        if (typeof tmplink.download === 'string') {
472          tmplink.href = "data:text/csv;charset=utf-8,"+encodeURIComponent(data);
473          tmplink.download = "arvid-"+makeDateString(date)+".csv";
474          document.body.appendChild(tmplink);
475          tmplink.click();
476          document.body.removeChild(tmplink);
477        } else {
478          window.location = "data:text/csv;charset=utf-8,"+encodeURIComponent(data);
479        }
480      }
481      
482      function makeDateString(date) {
483        var dateString = date.getFullYear().toString();
484        if (date.getMonth() < 9) { dateString += "0"; }
485        dateString += (date.getMonth()+1);
486        if (date.getDate() < 10) { dateString += "0"; }
487        dateString += date.getDate()+"-";
488        if (date.getHours() < 10) { dateString += "0"; }
489        dateString += date.getHours();
490        if (date.getMinutes() < 10) { dateString += "0"; }
491        dateString += date.getMinutes();
492        if (date.getSeconds() < 10) { dateString += "0"; }
493        dateString += date.getSeconds();
494        return(dateString);
495      }
496      
497      // main table view function
498      function viewTable () {
499        var values = [];                                                                                                                                                                                                                                         
500        var combinedNames = [];                                                                                                                                                                                                                                  
501        var combinedValues = [];                                                                                                                                                                                                                                 
502        if (combine <= 1) {                                                                                                                                                                                                                                      
503          for (var d=0;d<datapoints.length;d++) {                                                                                                                                                                                                                
504            values[d] = datapoints[d].value;                                                                                                                                                                                                                     
505            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }                                                                                                                                                                                  
506          }                                                                                                                                                                                                                                                      
507        } else if (! numericalData) {                                                                                                                                                                                                                            
508          error("only numerical data can be combined!\n");                                                                                                                                                                                                       
509          for (var d=0;d<datapoints.length;d++) {                                                                                                                                                                                                                
510            values[d] = datapoints[d].value;                                                                                                                                                                                                                     
511            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }                                                                                                                                                                                  
512          }                                                                                                                                                                                                                                                      
513        } else {                                                                                                                                                                                                                                                 
514          var combinations = combineValues();
515          for (var d=0;d<datapoints.length;d++) {
516            values[d] = combinations[d].value;
517            combinedValues[d] = combinations[d].combinedValues;
518            combinedNames[d] = combinations[d].combinedNames;
519          }
520        }                                                                                                                                                                                                                                                        
521        var table = '<table id="table" callspacing="5" border="1" class="sortable">\n';
522        table += "<thead><tr><th>
522Longitude</th><th>Latitude</th><th>Value</th><th>Name</th>";
523        if (combinedValues.length > 0) { table += "<th>Combined Names</th><th>Combined Values</th>"; }
524        table += "</tr></thead><tbody>\n";
525        // gues size for value input field
526        var valueLength = 5;
527        if (typeof(values[0]) !== undefined && values[0].toString().length > valueLength) { valueLength = values[0].toString().length; }
528        if (typeof(values[datapoints.length-1]) !== undefined && values[datapoints.length-1].toString().length > valueLength) { valueLength = values[datapoints.length-1].toString().length; }
529        for (var d=0;d<datapoints.length;d++) {
530            var valueField = '<input type="text" size="'+valueLength+'" onchange="updates['+d+']=this.value;" value="';
531            valueField += values[d];
532            valueField += '"/>';
533            table += "<tr><td>"+datapoints[d].longitude+"</td><td>"+datapoints[d].latitude+"</td><td>"+valueField+"</td><td>"+datapoints[d].name+"</td>\n";
534            if (combinedValues.length > 0) { table += "<td>"+combinedNames[d]+"</td><td>"+combinedValues[d]+"</td>"; }
535            table += "</tr>\n";
536        }
537        table += "</tbody></table>\n<p>"+datapoints.length+" data points</p>\n";
538        document.getElementById("map").style.width = "";
539        document.getElementById("map").style.height = "";
540        document.getElementById("map").innerHTML = table;
541        document.getElementById("legend").innerHTML = "";
542        document.getElementById("mapbuttons").style.visibility = "hidden";
543        document.getElementById("mapbuttons2").style.visibility = "hidden";
544        sorttable.makeSortable(document.getElementById("table"));
545      }
546
547      function combineValues() {
548        var distances = new Object;
549        var values = new Object;
550        var combinations = new Object;
551        var combinedValues = new Object;
552        var combinedNames = new Object;
553        if (combine > datapoints.length) {
554           combine = datapoints.length;
555           document.getElementById("combine").value = combine;
556           error("value of combine is larger than the number of points, adjusting value to "+combine+"...\n");
557        }
558        for (var d=0;d<datapoints.length;d++) {
559          for (var e=d+1;e<datapoints.length;e++) {
560            var xd = Number(datapoints[d].longitude);
561            var yd = Number(datapoints[d].latitude);
562            var xe = Number(datapoints[e].longitude);
563            var ye = Number(datapoints[e].latitude);
564            var distance = (xd-xe)*(xd-xe)+(yd-ye)*(yd-ye);
565            var keys = [d,e];
566            for (var k=0;k<keys.length;k++) {
567              var key = keys[k];
568              var pair = d; if (key == d) { pair = e; }
569              if (typeof(distances[key]) === "undefined") { distances[key] = [{"pair":pair, "distance":distance}]; }
570              else {
571                 var i = 0;
572                 while (i < distances[key].length && distance > distances[key][i].distance) { i++; }
573                 distances[key].splice(i,0,{"pair":pair, "distance":distance});
574              }
575            }
576          }
577        }
578        for (var d=0;d<datapoints.length;d++) {
579          var blanksSeen = 0;
580          var value = datapoints[d].value;
581          value = value.replace(/#.*$/,""); // remove all but first number
582          if (typeof(updates[d]) != "undefined") { value = updates[d]; }
583          if (value == "") { values[d] = ""; blanksSeen++; combinedValues[d] = "EMPTY"; }
584          else { values[d] = Number(value); combinedValues[d] = value; }
585          combinedNames[d] = datapoints[d].name;
586          for (var k=0;k<combine-1;k++) {
587            value = datapoints[distances[d][k].pair].value;
588            if (typeof(updates[distances[d][k].pair]) != "undefined") { value = updates[distances[d][k].pair]; }
589            if (value == "") { blanksSeen++; }
590            else {
591              if (values[d] == "") { values[d] = Number(value); }
592              else { values[d] += Number(value); }
593            }
594            if (value == "") { value = "EMPTY"; }
595            var name = datapoints[distances[d][k].pair].name;
596            var sep = "#";
597            if (k == combine-2) { sep = "#"; }
598            combinedValues[d] += sep+value;
599            combinedNames[d] += sep+name;
600          }
601          if (combine > blanksSeen) { 
602            values[d] /= combine-blanksSeen; 
603            // check for rounding errors
604            values[d] = fixRoundingErrors(values[d]);
605          }
606          combinations[d] = { "value":values[d], "combinedValues":combinedValues[d],"combinedNames":combinedNames[d] };
607        }
608        return(combinations);
609      }
610      
611      // main map function
612      function viewMap () {
613        // combine values
614        var combine = Number(document.getElementById("combine").value);
615        var values = [];
616        var combinedNames = [];
617        var combinedValues = [];
618        if (combine <= 1) {
619          for (var d=0;d<datapoints.length;d++) { 
620            values[d] = datapoints[d].value; 
621            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }
622          }
623        } else if (! numericalData) {
624          error("only numerical data can be combined!\n");
625          for (var d=0;d<datapoints.length;d++) { 
626            values[d] = datapoints[d].value; 
627            if (typeof(updates[d]) !== "undefined") { values[d] = updates[d]; }
628          }
629        } else {
630          var combinations = combineValues();
631          for (var d=0;d<datapoints.length;d++) { 
632            values[d] = combinations[d].value;
633            combinedValues[d] = combinations[d].combinedValues;
634            combinedNames[d] = combinations[d].combinedNames;
635          }
636        }
637        if (distance != 0) {
638          if (numericalData) { error("cannot use nonzero distance for numerical!\n"); }
639          else if (isNaN(distance)) { error("illegal distance target: "+distance+"!\n"); }
640          else {
641            for (var d=0;d<datapoints.length;d++) {
642              if (typeof(datapoints[d].distance) === "undefined") { values[d] = ""; }
643              else if (isNaN(datapoints[d].distance)) { error("illegal numeric value "+datapoints[d].distance+" for location "+datapoints[d].name+"!\n"); values[d] = ""; }
644              else if (Number(datapoints[d].distance) >= Number(distance)) { values[d] = "MoreThanDistance"; }
645              else { values[d] = "LessThanDistance"; }
646            }
647          }
648        }
649        if (distance == 0  && transition > 0) { // transition zone requested
650          if (datapoints.length == 0 || typeof(datapoints[0].numbers) == "undefined" || ! /,/.test(datapoints[0].numbers)) {
651            error("cannot compute transition zones: missing data\n");
652          } else {
653            var margins = [];
654            for (var d=0;d<datapoints.length;d++) {
655              var numbers = datapoints[d].numbers.split(/,/);
656              margins[d] = [];
657              margins[d].index = d;
658              margins[d].margin = Math.abs(Number(numbers[1])-Number(numbers[0]));
659              for (var m=2;m<numbers.length;m++) {
660                for (var n=0;n<m;n++) {
661                  var margin = Math.abs(Number(numbers[m])-Number(numbers[n]));
662                  if (margin < margins[d].margin) { margins[d].margin = margin; }
663                }
664              }
665            }
666            margins.sort(function(a,b) { return(a.margin-b.margin); })
667            for (var i=0;i<transition;i++) { values[margins[i].index] = "transition zone"; }
668          }
669        }
670        // read coordinates
671        var sites = [];
672        var xLeft;
673        var xRight;
674        var yTop;
675        var yBottom;
676        var minValue;
677        var maxValue;
678        var minValue2;
679        var maxValue2;
680        var minValue3;
681        var maxValue3;
682        for (var d=0;d<datapoints.length;d++) {
683          var x = Number(datapoints[d].longitude);
684          var y = Number(datapoints[d].latitude);
685          var value = values[d];
686          if (numericalData && value != "") {
687             var fields = value.split("#");
688             if (typeof(maxValue) === "undefined" || Number(fields[0]) > maxValue) { maxValue = Number(fields[0]); }
689             if (typeof(minValue) === "undefined" || Number(fields[0]) < minValue) { minValue = Number(fields[0]); }
690             if (typeof(fields[1]) !== "undefined") {
691                if (typeof(maxValue2) === "undefined" || Number(fields[1]) > maxValue2) { maxValue2 = Number(fields[1]); }
692                if (typeof(minValue2) === "undefined" || Number(fields[1]) < minValue2) { minValue2 = Number(fields[1]); }
693             }
694             if (typeof(fields[2]) !== "undefined") {
695                if (typeof(maxValue3) === "undefined" || Number(fields[2]) > maxValue3) { maxValue3 = Number(fields[2]); }
696                if (typeof(minValue3) === "undefined" || Number(fields[2]) < minValue3) { minValue3 = Number(fields[2]); }
697             }
698          }
699          var combinedValue = "";
700          if (typeof(combinedValues[d]) !== "undefined") { combinedValue = " (combined: "+combinedValues[d]+")"; }
701          var combinedName = "";
702          if (typeof(combinedNames[d]) !== "undefined") { combinedName = " (combined: "+combinedNames[d]+")"; }
703          sites.push({'x':x,'y':y,'d':value,'name':datapoints[d].name+combinedName,'combinedValues':combinedValue});
704          if (typeof(xLeft)  === "undefined" || x < xLeft)  {  xLeft = x; }
705          if (typeof(xRight) === "undefined" || x > xRight) { xRight = x; }
706          if (typeof(yTop)   === "undefined" || y > yTop)   {   yTop = y; }
707          if (typeof(yBottom)  === "undefined" || y < yBottom)  {  yBottom = y; }
708        }
709        // if (numericalData && datapoints.length > 0 && minValue == maxValue) { numericalData = false; }
710        // this doesn't work if a pole or the date line lies in the area
711        var xCenter = (xLeft+xRight)/2;
712        var yCenter = (yTop+yBottom)/2;
713        var xDiff = Math.abs(xRight-xLeft);
714        var yDiff = Math.abs(yTop-yBottom);
715        // assumption: yDiff=>zoom: 2.8=>7 1.4=>8 5.6=>6
716        var zoomY = Math.round(7-Math.log(yDiff/2.8)/Math.log(2));
717        var zoomX = Math.round(8-Math.log(xDiff/5)/Math.log(2));
718        var zoom = zoomY;
719        if (zoomX < zoomY) { zoom = zoomX; }
720        if (maxDistance < 0) { // unset: set default value
721          maxDistance = 0.28*yDiff/2.8; // estimated from node distribution
722          maxDistance = Math.floor(10000*maxDistance)/10000;
723          document.getElementById("maxDistance").value = maxDistance;
724        }
725        if (opacity < 0) {
726          opacity = 0.2;
727          document.getElementById("opacity").value = opacity;
728        }
729      
730        // data points: array of {longitude,latitude,value}
731        // (in array "sites" in file sites.js)
732        // computer voronoi areas
733        var bbox = {xl:xLeft-2*maxDistance,xr:xRight+2*maxDistance,yb:yTop+2*maxDistance,yt:yBottom-2*maxDistance};
734        var voronoi = new Voronoi();
735        var diagram = voronoi.compute(sites, bbox);
736        var cells = diagram.cells;
737        var colors = [
738          'rgba(255, 255, 255, '+opacity+')',      // white
739          'rgba(255,   0,   0, '+opacity+')',      // red
740          'rgba(  0, 255,   0, '+opacity+')',      // green
741          'rgba(  0,   0, 255, '+opacity+')',      // blue
742          'rgba(255, 255,   0, '+opacity+')',      // yellow
743          'rgba(255,   0, 255, '+opacity+')',      // magenta
744          'rgba(  0, 255, 255, '+opacity+')',      // cyan
745          'rgba(127,   0,   0, '+opacity+')',      // light red
746          'rgba(  0, 127,   0, '+opacity+')',      // light green
747          'rgba(  0,   0, 127, '+opacity+')',      // light blue
748          'rgba(127, 127,   0, '+opacity+')',      // light yellow
749          'rgba(127,   0, 127, '+opacity+')',      // light magenta
750          'rgba(  0, 127, 127, '+opacity+')',      // light cyan
751          'rgba(  0,   0,   0, '+opacity+')'];     // black
752        var polygons = [];
753        var points = [];
754        for (var i=0;i<cells.length;i++) {
755          // process edges
756          var edges = new Array;
757          for (var j=0;j<cells[i].halfedges.length;j++) {
758            edges.push([cells[i].halfedges[j].edge.va.x,cells[i].halfedges[j].edge.va.y]);
759            edges.push([cells[i].halfedges[j].edge.vb.x,cells[i].halfedges[j].edge.vb.y]);
760          }
761          edges = sortEdges(edges);
762          edges = clipEdges(cells[i].site.x,cells[i].site.y,edges,maxDistance);
763          var key = cells[i].site.d+" "+i;
764          if (typeof(polygons[key]) === "undefined") { polygons[key] = []; }
765          points.push([cells[i].site.x,cells[i].site.y]);
766          polygons[key].push([edges[0]]);
767          var polygonsLength = polygons[key].length
768          for (var k=2;k<edges.length;k+=2) { polygons[key][polygonsLength-1].push(edges[k]); }
769        }
770      
771        var iconFeatures = new Object;
772        var vectorSources = new Object;
773        var iconStyles = new Object;
774        var vectorLayers = new Object;
775      
776        // draw the points
777        var pointList = [];
778        for (var i=0;i<points.length;i++) {
779          var pointFeatures = new ol.Feature({ geometry: new ol.geom.Point([points[i][0],points[i][1]]).transform('EPSG:4326', 'EPSG:3857') });
780          pointList.push(pointFeatures);
781        }
782        vectorSources['points'] = new ol.source.Vector({ features: pointList });
783        iconStyles['points'] = new ol.style.Style({
784          image: new ol.style.Circle({
785            radius: 2,
786            fill: new ol.style.Fill({color: 'black'})
787          }),
788          stroke: new ol.style.Stroke({
789            color: 'black',
790            width: 2
791          }),
792          fill: new ol.style.Fill({
793            color: 'black'
794          }),
795        });
796        vectorLayers['points'] = new ol.layer.Vector({
797          source: vectorSources['points'],
798          style: iconStyles['points']
799        });
800      
801        // sort the polygon keys
802        var polygonKeys = [];
803        for (var key in polygons) { polygonKeys.push(key); }
804        polygonKeys.sort();
805        // only use color=white for unknown/empty data values
806        var colorsIndex = 1;
807        var polygonColors = new Object;
808        polygonColors[""] = colors[0];
809        for (var k=0;k<polygonKeys.length;k++) {
810          if (typeof(polygonKeys[k]) == "undefined") { polygonKeys[k] = ""; }
811          var key = polygonKeys[k];
812          var shortKey = key.replace(/\s+\d+$/,"");
813          index = key.replace(/.*\s/,"");
814          iconFeatures[key] = new ol.Feature({ geometry: new ol.geom.Polygon(polygons[key]).transform('EPSG:4326', 'EPSG:3857'), value:key, name:cells[index].site.name, combinedValues:cells[index].site.combinedValues });
815          vectorSources[key] = new ol.source.Vector({ features: [ iconFeatures[key] ] });
816          var thisColor = "rgba(0,0,0,1)"; // black
817          if (numericalData) {
818            var fields = shortKey.split("#");
819            shortKey = fields[0];
820            var shortKey2;
821            var shortKey3;
822            if (typeof(fields[1]) !== "undefined") { shortKey2 = fields[1]; }
823            if (typeof(fields[2]) !== "undefined") { shortKey3 = fields[2]; }
824            if (shortKey == "") { thisColor = polygonColors[""]; }
825            else {
826              var blueFactor = 255-Math.floor(255*0.5); // default for minValue == maxValue
827              var redFactor = 0; // default
828              var greenFactor = 255; // default
829              if (minValue != maxValue) { blueFactor = 255-Math.floor(255*(maxValue-shortKey)/(maxValue-minValue)); }
830              if (typeof(minValue2) !== "undefined" && minValue2 != maxValue2) { redFactor = 255-Math.floor(255*(shortKey2-minValue2)/(maxValue2-minValue2)); }
831              if (typeof(minValue3) !== "undefined" && minValue3 != maxValue3) { greenFactor = 255-Math.floor(255*(maxValue3-shortKey3)/(maxValue3-minValue3)); }
832              thisColor = 'rgba('+redFactor+','+blueFactor+','+greenFactor+','+opacity+')';
833              thisColor = paletteF(thisColor);
834            }
835          } else if (typeof(polygonColors[shortKey]) !== "undefined") {
836            thisColor = polygonColors[shortKey];
837          } else { 
838            thisColor = colors[colorMap[colorsIndex]];
839            colorsIndex++; 
840            thisColor = paletteF(thisColor);
841            polygonColors[shortKey] = thisColor;
842          }
843          iconStyles[key] = new ol.style.Style({
844            image: new ol.style.Circle({
845              radius: 10,
846              fill: new ol.style.Fill({color: 'black'})
847            }),
848            stroke: new ol.style.Stroke({
849              color: "black",
850              width: 0
851            }),
852            fill: new ol.style.Fill({
853              color: thisColor
854            }),
855          });
856          vectorLayers[key] = new ol.layer.Vector({
857            source: vectorSources[key],
858            style: iconStyles[key]
859          });
860          if (colorsIndex >= colors.length) { colorsIndex = 1; error("too many colors required!"); }
861        }
862      
863        var OSMbase = new ol.layer.Tile({ source: new ol.source.OSM() })
864        var layers = [];
865        if (typeof(document.getElementById("backgroundMap").value) != "undefined" &&
866            document.getElementById("backgroundMap").value == "on") { layers = [OSMbase]; }
867        for (var key in vectorLayers) { if (key != 'points') { layers.push(vectorLayers[key]); } }
868        layers.push(vectorLayers['points']); // last layer to add: should be visible
869        document.getElementById("map").style.width = "700";
870        document.getElementById("map").style.height = "525";
871        document.getElementById("map").innerHTML = "";
872        document.getElementById("mapbuttons").style.visibility = "visible";
873        document.getElementById("mapbuttons2").style.visibility = "visible";
874      
875        var map = new ol.Map({
876          layers: layers,
877          renderer: 'canvas',
878          target: 'map',
879          controls: ol.control.defaults({
880            attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
881              collapsible: false
882            })
883          }),
884          //center: ol.proj.transform([4.834167,52.143333], 'EPSG:4326', 'EPSG:3857'),
885          view: new ol.View({
886            center: ol.proj.transform([xCenter,yCenter], 'EPSG:4326', 'EPSG:3857'),
887            zoom: zoom
888          })
889        });
890      
891        // process click on map for extra information
892        // source: http://gis.stackexchange.com/questions/124137/openlayers-foreachfeatureatpixel-finds-point-when-clicking-in-the-lower-part-or
893        map.on('singleclick', function(evt) {
894          document.getElementById("comment").innerHTML = "";
895          var allFeaturesAtPixel = [];
896          map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) { allFeaturesAtPixel.push(feature); });
897          for (var i=0; i<allFeaturesAtPixel.length; i++) {
898            if (typeof(allFeaturesAtPixel[i].get('value')) !== "undefined") {
899              var shortKey = allFeaturesAtPixel[i].get('value').replace(/\s+\d+$/,"");
900              document.getElementById("comment").innerHTML += 'name: '+allFeaturesAtPixel[i].get('name')+'<br>value: '+shortKey+allFeaturesAtPixel[i].get('combinedValues')+'<br>';
901            }
902          }
903        });
904      
905        // make legend
906// 20150603 see also: http://codepen.io/guyfedwards/pen/xcfEb
907        document.getElementById("legend").innerHTML = datapoints.length+" data points | ";
908        if (! numericalData) {
909          var block = "";
910          var counter = 0;
911          for (var k in polygonColors) {
912            if (k != "") {
913              block += "<font style=\"background:"+color2hex(polygonColors[k])+"; opacity: "+opacity+";\" value=\"0\">&nbsp;&nbsp;&nbsp;</font>\n";
914              block += '<select id="legend-'+counter+'" onchange="colorMap['+(counter)+']=this.selectedIndex; viewMap();">\n';
915              block += "<option style=\"background:"+color2hex(polygonColors[k])+"; value=\"0\">value: "+k+"</option>\n";
916              for (var c=1;c<=13;c++) {
917                var bgcolor = colors[c];
918                bgcolor = paletteF(bgcolor);
919                //block += "<option style=\"background:"+color2hex(colors[c])+"; opacity: "+opacity+";\" value=\""+c+"\">&nbsp;&nbsp;&nbsp; "+colorNames[c]+"</option>\n";
920                block += "<option style=\"background:"+color2hex(bgcolor)+"; value=\""+c+"\">&nbsp;&nbsp;&nbsp; "+"   "+"</option>\n"; // "   " was colornames[c]
921              }
922              block += '</select> |\n';
923            }
924            counter++;
925          }
926          document.getElementById("legend").innerHTML += block;
927        }
928      }
929      
930      // convert color string to hex code
931      // input example: rgba(255, 255, 0, 0.2)
932      function color2hex(string) {
933        string = string.replace(/[^0-9]+/g,"#");
934        var fields = string.split("#");
935        while (fields.length > 0 && fields[0] == "") { fields.splice(0,1); }
936        var nbr1 = Number(fields[0]).toString(16); if (fields[0] < 16) { nbr1 = "0"+nbr1; }
937        var nbr2 = Number(fields[1]).toString(16); if (fields[1] < 16) { nbr2 = "0"+nbr2; }
938        var nbr3 = Number(fields[2]).toString(16); if (fields[2] < 16) { nbr3 = "0"+nbr3; }
939        return("#"+nbr1+nbr2+nbr3);
940      }
941      
942      // put edges in the right order
943      // format of parameter edges: list of edge poiints: edges[i]->edges[i+1]
944      function sortEdges(edges) {
945        var i = 2; // don't change location of first edge
946        while (i < edges.length) {
947          var j = i;
948          var x = edges[i-1][0];
949          var y = edges[i-1][1];
950          // find point that matches previous edge: edges should connect like a circle
951          while (j < edges.length-1 && (edges[j][0] != x || edges[j][1] != y)) { j++; }
952          if (edges[j][0] != x || edges[j][1] != y) {
953             if (x == edges[0][0] && y == edges[0][1]) {
954                edges.splice(i); // HACK! throws away redundant edges but where did they come from???
955             } else {
956                error("sortEdges: problem with linking edges! "+i+": ("+x+","+y+") not in: "+edges);
957             }
958          } else {
959            // test if common point lies art the end of the edge
960            if (j%2 != 0) { // switch points
961              var keepx = edges[j][0];
962              var keepy = edges[j][1];
963              edges[j][0] = edges[j-1][0];
964              edges[j][1] = edges[j-1][1];
965              edges[j-1][0] = keepx;
966              edges[j-1][1] = keepy;
967              j--;
968            }
969            // move edge to the correct position if necessary
970            if (j > i) {
971              var keepx1 = edges[j][0];
972              var keepy1 = edges[j][1];
973              var keepx2 = edges[j+1][0];
974              var keepy2 = edges[j+1][1];
975              edges[j][0] = edges[i][0];
976              edges[j][1] = edges[i][1];
977              edges[j+1][0] = edges[i+1][0];
978              edges[j+1][1] = edges[i+1][1];
979              edges[i][0] = keepx1;
980              edges[i][1] = keepy1;
981              edges[i+1][0] = keepx2;
982              edges[i+1][1] = keepy2;
983            }
984          }
985          i += 2;
986        }
987        return(edges);
988      }
989      
990      // map points from to line between (x0,y0) and (x1,y1) to circle and add these to polygon
991      function addCurve(nodes,sitex,sitey,x0,y0,x1,y1,nbrOfPoints) {
992        // now map all points
993        for (var j=0;j<=nbrOfPoints;j++) {
994          var x = x0+(x1-x0)*j/nbrOfPoints;
995          var y = y0+(y1-y0)*j/nbrOfPoints;
996          var distance = Math.sqrt((x-sitex)*(x-sitex)+(y-sitey)*(y-sitey));
997          var newx = sitex+(x-sitex)*maxDistance/distance;
998          var newy = sitey+(y-sitey)*maxDistance/distance;
999          nodes.push([newx,newy]);
1000        }
1001        return(nodes);
1002      } 
1003      
1004      // clip the edges if they are too long
1005      // center point: (sitex,sitey)
1006      // edges: ((x1,y1),(x2,y2),...,(xn,yn)) with xn=x1 and yn=y1
1007      function clipEdges(sitex,sitey,edges,maxDistance) {
1008        var distances = [];
1009        for (var i=0;i<edges.length;i+=2) {
1010          var x0 = edges[i][0];
1011          var y0 = edges[i][1];
1012          var distance0 = Math.sqrt((x0-sitex)*(x0-sitex)+(y0-sitey)*(y0-sitey));
1013          var x1 = edges[i+1][0];
1014          var y1 = edges[i+1][1];
1015          var distance1 = Math.sqrt((x1-sitex)*(x1-sitex)+(y1-sitey)*(y1-sitey));
1016          if (distance0 <= maxDistance && distance1 <= maxDistance) { ///
1017            // both edge points are near the site: use them
1018            distances.push([x0,y0]); distances.push([x1,y1]);
1019          } else if (distance0 > maxDistance && distance1 > maxDistance) { ///
1020            // both edge points are far away from the site
1021            // check if line intersects with bounding circle
1022            var points = findIntersectionPoints(sitex,sitey,maxDistance,x0,y0,x1,y1);
1023            if (typeof(points[1]) !== "undefined" &&
1024                ((x0 <= points[0].x && points[0].x <= x1 && x0 <= points[1].x && points[1].x <= x1) ||
1025                 (x0 >= points[0].x && points[0].x >= x1 && x0 >= points[1].x && points[1].x >= x1)) &&
1026                ((y0 <= points[0].y && points[0].y <= y1 && y0 <= points[1].y && points[1].y <= y1) ||
1027                 (y0 >= points[0].y && points[0].y >= y1 && y0 >= points[1].y && points[1].y >= y1))) {
1028              // two intersection points found: use them in the right order
1029              if (((x0 <= x1 && points[0].x <= points[1].x) || (x0 >= x1 && points[0].x >= points[1].x)) &&
1030                  ((y0 <= y1 && points[0].y <= points[1].y) || (y0 >= y1 && points[0].y >= points[1].y))) {
1031                distances = addCurve(distances,sitex,sitey,x0,y0,points[0].x,points[0].y,9);
1032                distances.push([points[0].x,points[0].y]); 
1033                distances.push([points[1].x,points[1].y]);
1034                distances = addCurve(distances,sitex,sitey,points[1].x,points[1].y,x1,y1,9);
1035              } else {
1036                distances = addCurve(distances,sitex,sitey,x0,y0,points[1].x,points[1].y,9);
1037                distances.push([points[1].x,points[1].y]); 
1038                distances.push([points[0].x,points[0].y]);
1039                distances = addCurve(distances,sitex,sitey,points[0].x,points[0].y,x1,y1,9);
1040              }
1041            } else {
1042              // one or no intersection points found: map the edge points to the bounding circle plus some intermediate points
1043              distances = addCurve(distances,sitex,sitey,x0,y0,x1,y1,99);
1044            }
1045          } else {
1046            // one edge point is near the site, the other is far away
1047            // find out where the line intersects with the bounding circle
1048            var points = findIntersectionPoints(sitex,sitey,maxDistance,x0,y0,x1,y1);
1049            // the function will find two points: choose the one between the edge points
1050            var inbetween = [];
1051            if (((x0 <= points[0].x && points[0].x <= x1) || (x0 >= points[0].x && points[0].x >= x1)) &&
1052                ((y0 <= points[0].y && points[0].y <= y1) || (y0 >= points[0].y && points[0].y >= y1))) { 
1053              inbetween = [points[0].x,points[0].y]; 
1054            } else { inbetween = [points[1].x,points[1].y]; }
1055            if (distance0 <= maxDistance) { 
1056              distances.push([x0,y0]); 
1057              distances.push(inbetween);
1058              distances = addCurve(distances,sitex,sitey,inbetween[0],inbetween[1],x1,y1,9);
1059            } else { 
1060              distances = addCurve(distances,sitex,sitey,x0,y0,inbetween[0],inbetween[1],9);
1061              distances.push(inbetween); 
1062              distances.push([x1,y1]); 
1063            }
1064          }
1065        }
1066        var newEdges = [];
1067        for (var i=0;i<distances.length-1;i++) {
1068          if (distances[i][0] != distances[i+1][0] || distances[i][1] != distances[i+1][1]) {
1069            newEdges.push(distances[i]);
1070            newEdges.push(distances[i+1]);
1071          }
1072        }
1073        if (newEdges[0][0] != newEdges[newEdges.length-1][0] || newEdges[0][1] != newEdges[newEdges.length-1][1]) {
1074          newEdges.push(newEdges[newEdges.length-1]);
1075          newEdges.push(newEdges[0]);
1076        }
1077        return(newEdges);
1078      }
1079      
1080      function findIntersectionPoints(sitex,sitey,maxDistance,x0,y0,x1,y1) {
1081        if (x1 == x0 && y0 == y1) { 
1082          error("findIntersectionPoints called with duplicate points: "+x0+" "+y0);
1083          return([]); 
1084        } else if (x1-x0 != 0) { // non-vertical line 
1085          // find a point at the line x0,y0 - x1,y1 at maxDistance from center
1086          // formula of the line: y = y1 + (y2-y1)/(x2-x1)*(x-x1)
1087          // so: y = b + a*x with a = (y2-y1)/(x2-x1) and b = y1 - x1*a
1088          var a = (y1-y0)/(x1-x0);
1089          var b = (y0*x1-y1*x0)/(x1-x0);
1090          // formula of the circle: (x-sitex)^2 + (y-sitey)^2 = maxDistance^2
1091          // so: y = sitey+sqrt(maxDistance^2-(x-sitex)^2)
1092          // so: sitey+sqrt(maxDistance^2-(x-sitex)^2) = b + a*x
1093          // so: maxDistance^2-(x-sitex)^2 = (b + a*x - sitey)^2
1094          // so: -x^2 + 2*sitex*x - sitex^2 = -maxDistance^2 + (b-sitey)^2 + 2*(b-sitey)*a*x + a^2*x^2
1095          // so: -x^2 - a^2*x^2                             => x^2 * (-1-a^2)
1096          //     x*2*sitex - x*2*(b-sitey)*a                => x   * (2*sitex-2*a*(b-sitey))
1097          //     -sitex^2 + maxDistance^2 - (b-sitey)^2 = 0 => 1   * (-sitex^2 + maxDistance^2 - (b-sitey)^2)
1098          // now X1,X2 = (-b +- sqrt(b^2-4*a*c))/(2*a)
1099          var A = -1-a*a;
1100          var B = 2*sitex-2*a*(b-sitey);
1101          var C = -sitex*sitex+maxDistance*maxDistance-b*b+2*b*sitey-sitey*sitey;
1102          if (B*B >= 4*A*C) {
1103            var point1 = new Object;
1104            var point2 = new Object;
1105            point1.x = (-B+Math.sqrt(B*B-4*A*C))/(2*A);
1106            point2.x = (-B-Math.sqrt(B*B-4*A*C))/(2*A);
1107            point1.y = a*point1.x+b;
1108            point2.y = a*point2.x+b;
1109            if (B*B > 4*A*C) { return([point1,point2]); }
1110            else { return([point1]); }
1111          }
1112          return([]);
1113        } else { // vertical line: x = a*y+b
1114          var a = 0;
1115          var b = x0;
1116          // the computation of A, B and C is explained in the comments above
1117          var A = -1-a*a;
1118          var B = 2*sitey-2*a*(b-sitex);
1119          var C = -sitey*sitey+maxDistance*maxDistance-b*b+2*b*sitex-sitex*sitex;
1120          if (B*B >= 4*A*C) {
1121            var point1 = new Object;                                                                                                                                                                                                                                 
1122            var point2 = new Object;                                                                                                                                                                                                                                 
1123            point1.y = (-B+Math.sqrt(B*B-4*A*C))/(2*A);
1124            point2.y = (-B-Math.sqrt(B*B-4*A*C))/(2*A);
1125            point1.x = a*point1.y+b;
1126            point2.x = a*point2.y+b;
1127            if (B*B > 4*A*C) { return([point1,point2]); }
1128            else { return([point1]); }
1129          }
1130          return([]);
1131        }
1132      }
1133      
1134      function error(string) {
1135        document.getElementById("error").innerHTML += "<br><font color=\"red\">"+string+"</string>";
1136      }
1137      
1138      function cleanError() {
1139        document.getElementById("error").innerHTML = "";
1140        document.getElementById("comment").innerHTML = "";
1141      }
1142      
1143      function examples(code) {
1144        cleanError();
1145        if (typeof(code) === "undefined" || code == "0") { mainSitesFile = "default.kml"; }
1146        else if (code == "1") { mainSitesFile = "ex-newyork.kml"; }
1147        else if (code == "2") { mainSitesFile = "ex-erik1.kml"; }
1148        else if (code == "3") { mainSitesFile = "ex-erik2.kml"; }
1149        else if (code == "4") { mainSitesFile = "ex-nina1.kml"; }
1150        else if (code == "5") { mainSitesFile = "ex-basque21.kml"; }
1151        else if (code == "6") { mainSitesFile = "ex-basque22.kml"; }
1152        else if (code == "7") { mainSitesFile = "ex-basque23.kml"; }
1153        else if (code == "8") { mainSitesFile = "ex-basque24.kml"; }
1154        else if (code == "9") { mainSitesFile = "ex-basque25.kml"; }
1155        else if (code == "10") { mainSitesFile = "ex-basque26.kml"; }
1156        else if (code == "11") { mainSitesFile = "ex-basque27.kml"; }
1157        else if (code == "12") { mainSitesFile = "ex-basque28.kml"; }
1158        else if (code == "13") { mainSitesFile = "ex-basque29.kml"; }
1159        else if (code == "14") { mainSitesFile = "ex-spruit.kml"; }
1160        else { mainSitesFile = "default.kml"; }
1161        processData(mainSitesFile);                                                                                                                                                                                                                                    
1162        if (typeof(document.getElementById("mode").value) != "undefined" &&                                                                                                                                                                                            
1163            document.getElementById("mode").value == "table") { viewTable(); }                                                                                                                                                                                         
1164        else { viewMap(); }                                                                                                                                                                                                                                            
1165      }
1166
1167      function checkCGI() {
1168        var search = window.location.search;
1169        search = search.replace(/^\?/,"");
1170        var fields = search.split(/&/);
1171        for (var i=0;i<fields.length;i++) {
1172          var parts = fields[i].split(/=/);
1173          if (typeof(parts[1]) !== "undefined") {
1174            if (parts[0] == "backgroundMap") { document.getElementById("backgroundMap").value = parts[1]; }
1175            else if (parts[0] == "combine") { combine = Number(parts[1]); }
1176            else if (parts[0] == "example") { examples(parts[1]); }
1177            else if (parts[0] == "maxDistance") { document.getElementById("maxDistance").value = parts[1]; }
1178            else if (parts[0] == "mode") { mode = parts[1]; }
1179            else if (parts[0] == "opacity") { document.getElementById("opacity").value = parts[1]; }
1180            else if (parts[0] == "transition") { transition = Number(parts[1]); }
1181          }
1182        }
1183      }
1184
1185      function fixRoundingErrors(value) {
1186        var string = value.toString();
1187        if (/\./.test(string)) {
1188          if (/0000[0-4]$/.test(string)) { string = string.replace(/([^0])0*0000[0-4]$/,"$1"); }
1189          else if (/9999[5-9]$/.test(string)) { string = string.replace(/([^9])9*9999[5-9]$/,"$1"); } // rounding down here?
1190          value = Number(string);
1191        }
1192        return(value);
1193      }
1194
1195      function paletteF(thisColor) {
1196        var fields = thisColor.split(/[,()]/);
1197        var red = fields[1];
1198        var green = fields[2];
1199        var blue = fields[3];
1200        var opacity = fields[4];
1201        var RGBcolors = [red,green,blue]; // default
1202        var MAXcolor = 255;
1203        var nbrOfSwitches = 8;
1204        if (isNaN(palette)) { palette = 0; }
1205        while (palette < 0) { window.palette += 48; }
1206        while (palette >= 48) { window.palette -= 48; }
1207        document.getElementById('palette').value = palette;
1208        var colorSwitch = Math.floor(palette/nbrOfSwitches);
1209        if (colorSwitch == 1) { RGBcolors = [green,red,blue]; }
1210        else if (colorSwitch == 2) { RGBcolors = [blue,green,red]; }
1211        else if (colorSwitch == 3) { RGBcolors = [red,blue,green]; }
1212        else if (colorSwitch == 4) { RGBcolors = [green,blue,red]; }
1213        else if (colorSwitch == 5) { RGBcolors = [blue,red,green]; }
1214        var remainder = palette-nbrOfSwitches*colorSwitch;
1215        var switch1 = Math.floor(remainder/4);
1216        var switch2 = Math.floor((remainder-4*switch1)/2);
1217        var switch3 = remainder-4*switch1-2*switch2;
1218        if (switch1 > 0) { RGBcolors[0] = MAXcolor-RGBcolors[0]; }
1219        if (switch2 > 0) { RGBcolors[1] = MAXcolor-RGBcolors[1]; }
1220        if (switch3 > 0) { RGBcolors[2] = MAXcolor-RGBcolors[2]; }
1221        thisColor = 'rgba('+RGBcolors[0]+','+RGBcolors[1]+','+RGBcolors[2]+','+opacity+')';
1222        return(thisColor);
1223      } 
1224    </script>
1224
1225  </body>
1226</html>

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.