Working on Firefox, including URL fetch.

URL data not working on Chrome or Safari. Bother.
This commit is contained in:
Simon Brooke 2020-02-25 14:17:02 +00:00
parent 47ca5b17ba
commit a081358564
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
3 changed files with 133 additions and 19 deletions

View file

@ -2,6 +2,14 @@
An even more ultra-lightweight tool to show comma-separated value data on a map.
## Other variants
This is a little project I've played about with, and there are now three variants:
1. [geocsv](https://github.com/simon-brooke/geocsv) is a fairly heavyweight web-app with both client-side and serverside components. It was the first version, and is the only version which meets the original requirement of being able to present data from [Google Sheets](https://www.google.co.uk/sheets/about/), but it's a remarkably heavyweight solution to what should be a simple problem.
2. [geocsv-lite](https://github.com/simon-brooke/geocsv-lite) is a much lighter, client-side only reworking of the problem, in ClojureScript. I still wasn't satisfied that this was light enough.
3. [geocsv-js](https://github.com/simon-brooke/geocsv-js) is a reworking in native JavaScript without any frameworks or heave libraries, except Leaflet. It is vastly lighter, and probably the one to use in most applications.
## Overview
This is a third iteration of GeoCSV. The [original](https://github.com/simon-brooke/geocsv) was written quickly in Clojure and ClojureScript, with CSV parsing done server side and React (via [re-frame](https://github.com/day8/re-frame)) driving the client side. That's my comfort zone; but it had the benefit that my customer wanted to pull data from Google Sheets, which you can't do from client side (or at least I don't know how to) because of cross-site scripting protections.

View file

@ -122,8 +122,8 @@ crossorigin=""/>
<li>
For each <code>div</code> which you wish to contain a map view,
an invocation of the function
<code>geocsv_lite.core.initialise_map_element(id, data-source)</code>: <br/>
<samp>&lt;script&gt;geocsv_lite.core.initialise_map_element("map", "data/data.csv");&lt;/script&gt;</samp>
<code>geocsv_lite.core.initialiseMapElement(id, data-source)</code>: <br/>
<samp>&lt;script&gt;geocsv_lite.core.initialiseMapElement("map", "data/data.csv");&lt;/script&gt;</samp>
</li>
</ol>
<p>
@ -159,6 +159,12 @@ crossorigin=""/>
and you don't have an appropriate pin image for each value present,
then you will get 'broken' pin images appearing on your map.
</p>
<h2>
GitHub repository
</h2>
<p>
Is <a href="https://github.com/simon-brooke/geocsv-js">here.</a>
</p>
</div>
</div>
<footer>
@ -181,10 +187,10 @@ crossorigin=""></script>
<script src="js/geocsv.js" type="text/javascript"></script>
<script>
/* Map using data from element content */
GeoCSV.initialise_map_element("element-content-map",
GeoCSV.initialiseMapElement("element-content-map",
document.getElementById("element-content-map").innerText);
/* Map using inline CSV passed to the function */
GeoCSV.initialise_map_element("inline-csv-map",
GeoCSV.initialiseMapElement("inline-csv-map",
"Country,Name,Latitude,Longitude,CountryCode,Continent,Category\n" +
"Somaliland,Hargeisa,9.55,44.05,NULL,Africa,\n" +
"Western Sahara,El-Aaiún,27.153611,-13.203333,EH,Africa,EH\n" +
@ -245,8 +251,16 @@ crossorigin=""></script>
"Zimbabwe,Harare,-17.8166666666667,31.033333,ZW,Africa,ZW\n" +
"British Indian Ocean Territory,Diego Garcia,-7.3,72.4,IO,Africa,\n" );
/* Map using CSV from URL */
var url = window.location.href.substring(0, window.location.href.length - "index.html".length) + "data/europe-capitals.csv";
GeoCSV.initialise_map_element("url-map", url);
console.log( "Window.location.href = `" + window.location.href + "`");
var url = window.location.href;
if (url.endsWith( "index.html")) {
url = url.substring(0, window.location.href.length - "index.html".length);
}
url = url + "data/europe-capitals.csv";
GeoCSV.initialiseMapElement("url-map", url);
</script>
</body>
</html>

View file

@ -5,11 +5,17 @@
*/
var GeoCSV = {
/**
* Methods for disentangling data.
*/
Data: {
/**
* Prepare a single record (Object) from the keys `ks` and values `vs`.
*/
prepareRecord( ks, vs) {
var record = new Object();
for ( i = 0; i < Math.min( ks.length, vs.length); i++) {
for ( i = 0; i < Math.min( ks.length, vs.length); i += 1) {
if ( ks[ i]) {
record[ ks[ i]] = vs[ i];
}
@ -18,6 +24,11 @@ var GeoCSV = {
return record;
},
/**
* Prepare an array record objects from this `data`, assumed to be data
* as parsed by [PapaParse](https://www.papaparse.com/) from a CSV file
* with column headers in the first row.
*/
prepareRecords( data) {
var cols = data[0].map( c => {
return c.trim().toLowerCase().replace( /[^\w\d]+/, "-");
@ -39,13 +50,17 @@ var GeoCSV = {
return result;
},
getData( data_source) {
var p = Papa.parse( data_source);
/**
* Parse this `dataSource` and return it as record objects.
* Doesn't yet work for URLs.
*/
getData( dataSource) {
var p = Papa.parse( dataSource);
var data = p.data;
if ( p.errors.length > 0) {
try {
data = JSON.parse( data_source);
data = JSON.parse( dataSource);
}
catch( anything) {
data = null;
@ -55,11 +70,19 @@ var GeoCSV = {
if ( data instanceof Array) {
return this.prepareRecords( data);
} else {
// this is where I should handle URLs.
return null;
}
}
},
/**
* Methods related to locating and presenting data on the map
*/
GIS: {
/**
* Return an appropriate pin image name for this `record`.
*/
pinImage( record) {
var c = record["category"];
@ -73,6 +96,10 @@ var GeoCSV = {
}
},
/**
* Return appropriate HTML formatted popup content for this
* `record`.
*/
popupContent( record) {
var c = "<h5>" + record[ "name"] + "</h5><table>";
@ -87,7 +114,10 @@ var GeoCSV = {
return c + "</table>";
},
addPin( record, index, view) {
/**
* Add an appropriate marker for this `record` on this `view`.
*/
addPin( record, view) {
var lat = Number( record[ "latitude"]);
var lng = Number( record[ "longitude"]);
@ -112,6 +142,9 @@ var GeoCSV = {
}
},
/**
* Remove all pins from this map `view`.
*/
removePins( view) {
view.eachLayer( l => {
if ( l instanceof L.marker) {
@ -122,6 +155,10 @@ var GeoCSV = {
return view;
},
/**
* Pan and zoom this map `view` to focus these `records`.
* TODO: This isn't working *nearly* as well as the ClojureScript version.
*/
computeBounds( view, records) {
if ( records.length > 0) {
var minLng = 180;
@ -152,23 +189,35 @@ var GeoCSV = {
}
},
/**
* Add a marker to this map `view` for each record in these `records`
* which has valid `latitude`and `longitude` properties, first removing
* any existing markers.
*/
refreshPins( view, records) {
this.removePins( view);
for ( i = 0; i < records.length; i++) {
if( records[i]) {
this.addPin( records[i], i, view);
records.forEach( r => {
if( r) {
this.addPin( r, view);
}
}
});
this.computeBounds( view, records);
}
},
/**
* Methods related to displaying the map.
*/
Map: {
views: new Object(),
didMount(id, lat, lng, zoom) {
/**
* Create a map overlaying the HTML element with this `id`, centered at
* these `lat` and `lng` coordinates, with this initial `zoom` value.
*/
createMap(id, lat, lng, zoom) {
var v = L.map( id).setView( {lat: lat, lon: lng}, zoom);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
@ -179,22 +228,34 @@ var GeoCSV = {
return v;
},
/**
* Add a map view to my named views overlaying the HTML element with this
* `id` and centered at these `lat` and `lng` coordinates, with this
* initial `zoom` value, provided that it does not already exist. Return
* the view.
*/
addView( id, lat, lng, zoom) {
/* can"t re-add a view to an element to which we"ve already added one */
if ( this.views[ id]) {
return this.views[ id];
} else {
var v = this.didMount( id, lat, lng, zoom);
var v = this.createMap( id, lat, lng, zoom);
this.views[ id] = v;
return v;
}
},
/**
* Get the view with this `id` from among my named views.
*/
getView( id) {
return this.views[ id];
}
},
/**
* Methods related to notification and logging.
*/
Notify: {
/**
* Show this error `m` to the user and log it.
@ -213,17 +274,48 @@ var GeoCSV = {
}
},
initialise_map_element( id, data_source) {
/**
* Initialise a map view overlaying the HTML element with this `id`, and
* decorate it with markers as specified in the data from this source.
*/
initialiseMapElement( id, dataSource) {
this.Notify.message( "initialise_map_element called with arguments id = `" +
id + "`");
var view = this.Map.addView( id, 0, 0, 0);
var records = this.Data.getData( data_source);
var records = this.Data.getData( dataSource);
if ( records instanceof Array) {
this.Notify.message( "Found " + records.length +
" records of inline data for map " + id);
this.GIS.refreshPins( view, records);
} else {
// is it a URL?
try {
fetch(dataSource)
.then((response) => {
console.debug( response.blob());
if (response.ok) {
return response.text();
} else {
throw new Error( "Bad response from server: " + response.status);
}
}).then( text => {
var records = this.Data.getData( text);
if ( records instanceof Array) {
this.Notify.message( "Found " + records.length +
" records of data for map " + id);
this.GIS.refreshPins( view, records);
} else {
throw new Error( "No data?");
}
});
} catch (error) {
this.Notify.error( error);
}
}
}
}