Commit d2432ea4 authored by Joris's avatar Joris

Use coordinates instead of google search

parent 7dd5c2b5
Pipeline #51143335 passed with stages
in 2 minutes and 33 seconds
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "map-marks",
"dependencies": {
"papaparse": "4.1.2",
"immupdate": "0.2.2"
},
"devDependencies": {
"browserify": "13.0.1",
"watchify": "3.7.0",
"babelify": "7.3.0",
"browserify": "13.0.1",
"watchify": "3.7.0",
"babelify": "7.3.0",
"babel-preset-es2015": "6.9.0",
"http-server": "0.9.0"
"http-server": "0.9.0"
},
"scripts": {
"start": "npm run server & npm run watch",
"server": "http-server ./public -p 8080 --silent",
......
,joris,xps15,10.03.2019 20:43,file:///home/joris/.config/libreoffice/4;
\ No newline at end of file
name,address,city,icon,color,diameter,infos,population
Paris,,Paris,building,yellow,20000,Capital and most populous city of France.,2 240 000
Tour Eiffel,Eiffel Tower,Paris,building,green,,Wrought iron lattice tower,
Tower Bridge,Tower Bridge,London,building,orange,,Combined bascule and suspension bridge,
Atomium,Atomium,Bruxelles,building,blue,,Building originally constructed for Expo 58,
name,lat,lng,city,icon,color,diameter,infos,population
Paris,48.855517,2.354603,Paris,building,yellow,20000,Capital and most populous city of France.,2240000
Tour Eiffel,48.858575,2.295018,Paris,building,green,,Wrought iron lattice tower,
Tower Bridge,51.505417,-0.075358,London,building,orange,,Combined bascule and suspension bridge,
Atomium,50.895205,4.342083,Bruxelles,building,blue,,Building originally constructed for Expo 58,
export function minus(xs, ys) {
return xs.filter(x => ys.indexOf(x) < 0);
return xs.filter(x => ys.indexOf(x) < 0)
}
import * as Papa from 'papaparse';
import update, { DELETE, updateKey } from 'immupdate';
import * as Papa from 'papaparse'
import update, { DELETE, updateKey } from 'immupdate'
import * as Functional from './functional';
import * as Dom from './dom';
import * as Functional from './functional'
import * as Dom from './dom'
export const requiredKeys = ['city'];
export const requiredKeys = ['lat', 'lng']
export function failureMessage(mark) {
const line = Object.keys(mark).map(k => mark[k]).join(',');
return `Parse error for “${line}”`;
const line = Object.keys(mark).map(k => mark[k]).join(',')
return `Parse error for “${line}”`
}
export function parseAddresses(text) {
const parsed = Papa.parse(text, { header: true, skipEmptyLines: true });
const marks = parsed.data.map(cleanMark);
const parsed = Papa.parse(text, { header: true, skipEmptyLines: true })
const marks = parsed.data.map(cleanMark)
console.log(marks)
return {
csvSuccess: marks.filter(isValid),
csvFailures: marks.filter(isNotValid)
};
}
}
function isValid(mark) {
return hasRequiredKeys(mark);
return hasRequiredKeys(mark)
}
function isNotValid(mark) {
const ignore = Object.keys(mark).map(k => mark[k]).join('').trim().length === 0;
return !ignore && !hasRequiredKeys(mark);
const ignore = Object.keys(mark).map(k => mark[k]).join('').trim().length === 0
return !ignore && !hasRequiredKeys(mark)
}
function hasRequiredKeys(mark) {
return requiredKeys.map(key => mark[key]).indexOf(undefined) === -1;
return requiredKeys.map(key => mark[key]).indexOf(undefined) === -1
}
function cleanMark(mark) {
return Functional.foldLeft(Object.keys(mark), mark, (m, key) => {
return updateKey(m, key, trimOrDelete(m[key]));
});
return updateKey(m, key, trimOrDelete(m[key]))
})
}
function trimOrDelete(text) {
if(text) {
const trimmed = text.trim();
return trimmed ? trimmed : DELETE;
return text && text.trim() ? text.trim() : DELETE
} else {
return DELETE;
return DELETE
}
}
export function div(className, childs) {
return node('div', className, childs);
return node('div', className, childs)
}
export function text(str) {
return document.createTextNode(str);
return document.createTextNode(str)
}
function node(name, className, childs) {
const div = document.createElement(name);
if(className) div.classList.add(className);
if(childs) childs.forEach(child => div.appendChild(child));
return div;
const div = document.createElement(name)
if(className) div.classList.add(className)
if(childs) childs.forEach(child => div.appendChild(child))
return div
}
import * as Dom from './dom';
import * as Dom from './dom'
const body = document.getElementById('body');
const body = document.getElementById('body')
export function show(errors) {
clean();
body.appendChild(errorsNode(errors));
clean()
body.appendChild(errorsNode(errors))
document.querySelector('#body > .errors > .curtain').addEventListener('click', event => {
clean();
});
clean()
})
}
export function clean() {
const errors = document.querySelector('#body > .errors');
if(errors) body.removeChild(errors);
const errors = document.querySelector('#body > .errors')
if(errors) body.removeChild(errors)
}
function errorsNode(errors) {
return Dom.div('errors', [
Dom.div('curtain'),
Dom.div('list', errors.map(errorNode))
]);
])
}
function errorNode(error) {
return Dom.div('error', [
Dom.text(error)
]);
])
}
export function foldLeft(xs, sum, f) {
if(xs.length > 0) {
const head = xs[0];
const tail = xs.slice(1, xs.length);
return foldLeft(tail, f(sum, head), f);
const head = xs[0]
const tail = xs.slice(1, xs.length)
return foldLeft(tail, f(sum, head), f)
} else {
return sum
}
......
import * as Position from './position';
import * as Map from './map';
import * as CSV from './csv';
import * as Error from './error';
import * as Map from './map'
import * as CSV from './csv'
import * as Error from './error'
const input = document.getElementById('input');
const input = document.getElementById('input')
input.addEventListener('change', event => {
showLoader(true);
Error.clean();
showLoader(true)
Error.clean()
const reader = new FileReader();
const reader = new FileReader()
reader.onload = () => {
const { csvSuccess, csvFailures } = CSV.parseAddresses(reader.result);
const csvErrors = csvFailures.map(CSV.failureMessage);
const { csvSuccess, csvFailures } = CSV.parseAddresses(reader.result)
const csvErrors = csvFailures.map(CSV.failureMessage)
if(csvSuccess.length > 0) {
Position.fetch(csvSuccess)
.then(({ positionSuccess, positionFailures }) => {
const positionErrors = positionFailures.map(Position.failureMessage);
const errors = csvErrors.concat(positionErrors);
if(errors.length > 0) Error.show(errors);
showLoader(false, csvErrors.length > 0 ? 'orange' : 'green')
Map.show(csvSuccess, 8)
if(positionSuccess.length === 0) {
showLoader(false, 'red');
Map.show(positionSuccess, 8);
} else {
showLoader(false, errors.length > 0 ? 'orange' : 'green');
Map.show(positionSuccess, 8);
}
});
if (csvErrors.lengh > 0) Error.show(csvErrors)
} else {
showLoader(false, 'red');
Error.show(csvErrors);
showLoader(false, 'red')
Error.show(csvErrors)
}
};
}
reader.readAsText(input.files[0]);
});
reader.readAsText(input.files[0])
})
function showLoader(show, filenameClass) {
if(show) {
document.getElementById('loader').style.display = 'block';
document.querySelector('#filename > .text').innerHTML = '';
document.getElementById('loader').style.display = 'block'
document.querySelector('#filename > .text').innerHTML = ''
} else {
document.getElementById('loader').style.display = 'none';
document.getElementById('filename').className = filenameClass;
document.querySelector('#filename > .text').innerHTML = input.files[0].name;
document.getElementById('loader').style.display = 'none'
document.getElementById('filename').className = filenameClass
document.querySelector('#filename > .text').innerHTML = input.files[0].name
}
}
import * as Array from './array';
import * as Array from './array'
const defaultMarkerColor = 'red';
const defaultCircleColor = 'yellow';
const defaultMarkerColor = 'red'
const defaultCircleColor = 'yellow'
export function show(addresses, zoom) {
if(addresses.length > 0) {
const { position } = addresses[0];
document.getElementById('map').innerHTML = '<div id="mapContent"></div>';
const map = L.map('mapContent');
map.setView([position.lat, position.lng], zoom);
const { lat, lng } = addresses[0]
document.getElementById('map').innerHTML = '<div id="mapContent"></div>'
const map = L.map('mapContent')
map.setView([lat, lng], zoom)
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: 'Test'
}).addTo(map);
}).addTo(map)
const marks = addresses.map(getMark);
map.fitBounds(new L.featureGroup(marks).getBounds().pad(0.5));
marks.forEach(marker => marker.addTo(map));
const marks = addresses.map(getMark)
map.fitBounds(new L.featureGroup(marks).getBounds().pad(0.5))
marks.forEach(marker => marker.addTo(map))
}
}
function getMark(mark) {
const { name, address, icon, color, infos, diameter, position } = mark;
const { name, address, icon, color, infos, diameter, lat, lng } = mark
const element = diameter > 0
? getCircle(position, diameter, color)
: getMarker(position, name, icon, color);
? getCircle(lat, lng, diameter, color)
: getMarker(lat, lng, name, icon, color)
const description = getDescription(mark);
const description = getDescription(mark)
return description
? element.bindPopup(description)
: element;
: element
}
function getMarker(position, name, icon, color) {
function getMarker(lat, lng, name, icon, color) {
return L.marker(
position,
{ lat, lng },
{
title: name,
icon: getIcon(icon, color || defaultMarkerColor)
}
);
)
}
function getCircle(position, diameter, color) {
function getCircle(lat, lng, diameter, color) {
return L.circle(
position,
{ lat, lng },
diameter,
{
color: color || defaultCircleColor
}
);
)
}
function getDescription(mark) {
const { name } = mark;
const infos = getInfos(mark);
const additionalInfos = getInfos(mark);
const { name, link } = mark
const infos = getInfos(mark)
if(name && infos) {
return `<b>${name}</b><br>${infos}`;
} else if(name) {
return `<b>${name}</b>`;
} else if(infos) {
return `${infos}`;
} else {
return undefined;
}
const nameElement =
link !== undefined
? `<div><b><a href=${link}>${name}</a></b></div>`
: `<div><b>${name}</b></div>`
return `${nameElement}<div>${infos}</div>`
}
function getInfos(mark) {
const keys = ['name', 'address', 'city', 'icon', 'color', 'diameter', 'infos', 'position'];
const additionalKeys = Array.minus(Object.keys(mark), keys);
const { infos } = mark;
const keys = ['name', 'lat', 'lng', 'icon', 'color', 'diameter', 'infos', 'link']
const additionalKeys = Array.minus(Object.keys(mark), keys)
const { infos } = mark
const additionalInfos = additionalKeys
? additionalKeys.map(key => `<u>${key}</u> ${mark[key]}`).join('<br>')
: undefined;
: undefined
if(infos && additionalInfos) {
return `${infos}<br>${additionalInfos}`;
return `${infos}<br>${additionalInfos}`
} else if(infos || additionalInfos) {
return infos || additionalInfos;
return infos || additionalInfos
} else {
return undefined;
return undefined
}
}
......@@ -91,5 +87,5 @@ function getIcon(name, color) {
icon: name,
prefix: 'fa',
markerColor: color
});
})
}
import update from 'immupdate';
import * as Request from './request';
import * as Dom from './dom';
export function failureMessage(mark) {
const { address, city } = mark;
return `Fetch position error for “${address}, ${city}”`;
}
export function fetch(marks) {
return Promise.all(marks.map(fetchForMark))
.then(results => {
return {
positionSuccess: results.filter(result => result.position),
positionFailures: results.filter(result => !result.position)
};
});
}
function fetchForMark(mark) {
const { address, city } = mark;
if(address && city) {
return fetchPosition(mark, `${address} ${city}`).then(m => {
return m.position
? m
: fetchPosition(mark, city);
});
} else if(city) {
return fetchPosition(mark, city);
} else {
return mark;
}
}
function fetchPosition(mark, search) {
return Request.get(googleUrl(search, 'json'))
.then(payload => update(mark, { position: googleExtractPosition(payload) }))
.catch(() => mark);
}
function safeJsonParse(payload) {
try {
return JSON.parse(payload);
} catch(err) {
return undefined;
}
}
/* Google Geocoder */
function googleUrl(search) {
return 'http://maps.googleapis.com/maps/api/geocode/json?address=' + encodeURIComponent(search);
}
function googleExtractPosition(payload) {
const json = safeJsonParse(payload);
if(json && json.results && json.results.length > 0) {
const result = json.results[0];
if(result.geometry && result.geometry.location) {
return result.geometry.location
} else {
return undefined;
}
} else {
return undefined;
}
}
export function get(url) {
return new Promise((resolve, reject) => {
const http = new XMLHttpRequest();
http.onreadystatechange = function() {
if(http.readyState === 4) {
if(http.status < 200 || http.status > 299) {
console.error('GET ' + http.status + ' for ' + url);
reject(http.response);
} else {
resolve(http.responseText);
}
}
};
http.open('GET', url, true);
http.send();
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment