Fix the build, update of the README

This commit is contained in:
Pieter Vander Vennet 2020-12-04 21:03:05 +01:00
parent 3304395d55
commit 043a13badc
7 changed files with 174 additions and 86 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
data/
.idea/
node_modules/
lib/
dist/
.cache/

View file

@ -6,6 +6,21 @@ LatLon2Country is a reverse geocoder, whose goal is to convert a location (latlo
It is specifically designed to work with opening_hours.js and MapComplete and borrows ideas from codegrid.
Usage
-----
Install with npm:
`npm install latlon2country`
Run the code:
`const coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/")
coder.CountryCodeFor(lon, lat)
`
If you want to selfhost the tiles (which is highly recommended), download [tiles.zip](tiles.zip), extract the zip to your server and point the constructor above to the correct path.
Base architecture
-----------------

122
client/countryCoder.ts Normal file
View file

@ -0,0 +1,122 @@
import * as turf from "turf";
export default class CountryCoder {
private readonly _host: string;
constructor(host: string) {
this._host = host;
}
lon2tile(lon: number, zoom: number): number {
return Math.floor((lon + 180) / 360 * Math.pow(2, zoom));
}
lat2tile(lat: number, zoom: number): number {
return Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom));
}
/* url --> ([callbacks to call] | result) */
private static readonly cache = {}
Fetch(z: number, x: number, y: number, callback: ((data: any) => void)): void {
const path = `${z}.${x}.${y}.json`;
// @ts-ignore
let cached: { callbacks: ((data: any) => void)[], data: any } = CountryCoder.cache[path];
if (cached !== undefined) {
if (cached.data !== null) {
// O, the data has already arrived!
callback(cached.data);
} else {
// We'll handle this later when the data is there
cached.callbacks.push(callback);
}
// Nothing more to do right now
return;
}
// No cache has been defined at this point -> we define the cache + callbacks store
cached = {
data: null,
callbacks: []
};
// @ts-ignore
CountryCoder.cache[path] = cached;
const url = this._host + "/" + path;
cached.callbacks.push(callback);
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function () {
var status = xhr.status;
if (status === 200) {
cached.data = xhr.response;
for (const callback of cached.callbacks) {
callback(xhr.response);
}
} else {
console.error("COULD NOT GET ", url)
}
};
xhr.send();
}
determineCountry(lon: number, lat: number, z: number, x: number, y: number, callback: ((countries: string[]) => void)): void {
this.Fetch(z, x, y, data => {
if (data.length !== undefined) {
// This is an array
// If there is a single element, we have found our country
if (data.length === 1) {
return data;
}
// The appropriate subtile is determined by zoom level + 1
const dx = this.lon2tile(lon, z + 1);
const dy = this.lat2tile(lat, z + 1);
// Determine the quadrant
// We determine the difference with what 'x' and 'y' would be for the upper left quadrant
const index = (dx - x * 2) + 2 * (dy - y * 2);
const state = data[index];
if (state === 0) {
// No country defined, probably international waters
callback([]);
}
const nextZoom = Number(state);
if (isNaN(nextZoom)) {
// We have found the country!
callback([state]);
}
// Last case: the next zoom level is given:
this.determineCountry(lon, lat, nextZoom, this.lon2tile(lon, nextZoom), this.lat2tile(lat, nextZoom), callback)
} else {
// We have reached an actual leaf tile with actual geometry
const geojson = data;
const countries = [];
for (const feature of geojson.features) {
const intersection = turf.intersect(turf.point([lon, lat]), feature)
if (intersection !== undefined) {
const country = feature.properties.country;
countries.push(country)
}
}
countries.sort();
callback(countries);
}
})
}
CountryCodeFor(lon: number, lat: number, callback: ((countries: string[]) => void)): void {
this.determineCountry(lon, lat, 0, 0, 0, callback);
}
}

View file

@ -1,82 +0,0 @@
import * as fs from "fs";
import * as turf from "turf";
function lon2tile(lon, zoom) {
return Math.floor((lon + 180) / 360 * Math.pow(2, zoom));
}
function lat2tile(lat, zoom) {
return Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom));
}
function Fetch(z, x, y, callback: ((data: any) => void)): void {
const data = fs.readFileSync(`../data/Simplified/${z}.${x}.${y}.json`, {encoding: "utf8"});
callback(JSON.parse(data));
}
function determineCountry(lon, lat, z, x, y, callback: ((countries: string[]) => void)): void {
Fetch(z, x, y, data => {
if (data.length !== undefined) {
// This is an array
// If there is a single element, we have found our country
if (data.length === 1) {
return data;
}
// The appropriate subtile is determined by zoom level + 1
const dx = lon2tile(lon, z + 1);
const dy = lat2tile(lat, z + 1);
// Determine the quadrant
// We determine the difference with what 'x' and 'y' would be for the upper left quadrant
const index = (dx - x * 2) + 2 * (dy - y * 2);
const state = data[index];
if (state === 0) {
// No country defined, probably international waters
callback([]);
}
const nextZoom = Number(state);
if (isNaN(nextZoom)) {
// We have found the country!
callback([state]);
}
// Last case: the next zoom level is given:
determineCountry(lon, lat, nextZoom, lon2tile(lon, nextZoom), lat2tile(lat, nextZoom), callback)
}else{
// We have reached an actual leaf tile with actual geometry
const geojson = data;
const countries = [];
for (const feature of geojson.features) {
const intersection = turf.intersect(turf.point([lon, lat]), feature)
if(intersection !== undefined){
const country = feature.properties.country;
countries.push(country)
}
}
countries.sort();
callback(countries);
}
})
}
function CountryCodeFor(lon, lat, callback: ((countries: string[]) => void)): void {
determineCountry(lon, lat, 0, 0, 0, callback);
}
function pr(countries) {
console.log(">>>>>", countries.join(";"))
}
CountryCodeFor(3.2, 51.2, pr)
CountryCodeFor(4.92119, 51.43995, pr)
CountryCodeFor(4.93189, 51.43552, pr)
CountryCodeFor(34.2581, 44.7536, pr)

View file

@ -1,9 +1,10 @@
{
"name": "latlon2country",
"version": "1.0.0",
"version": "1.0.3",
"description": "Convert coordinates into the containing country",
"main": "index.js",
"scripts": {
"build": "tsc -project .",
"test": "echo \"Error: no test specified\" && exit 1",
"generate-tiles": "cd generator && ts-node generate.ts"
},
@ -16,12 +17,14 @@
"author": "Pieter Vander Vennet",
"license": "GPL-3.0-or-later",
"dependencies": {
"turf": "^3.0.14",
"@types/node": "^14.14.10",
"jquery": "^3.5.1"
"jquery": "^3.5.1",
"turf": "^3.0.14"
},
"devDependencies": {
"@types/turf": "^3.5.32",
"js-yaml": "^3.14.0",
"osmtogeojson": "^3.0.0-beta.4"
"osmtogeojson": "^3.0.0-beta.4",
"typescript": "^4.1.2"
}
}

14
test.html Normal file
View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="client/countryCoder.ts"></script>
</head>
<body>
<script src="./test.ts"></script>
</body>
</html>

13
test.ts Normal file
View file

@ -0,0 +1,13 @@
import CountryCoder from "./client/countryCoder";
console.log("Testing...")
function pr(countries: string[]) {
console.log(">>>>>", countries.join(";"))
}
console.log("Hi world")
const coder = new CountryCoder("http://127.0.0.1:8081/");
coder.CountryCodeFor(3.2, 51.2, pr)
coder.CountryCodeFor(4.92119, 51.43995, pr)
coder.CountryCodeFor(4.93189, 51.43552, pr)
coder.CountryCodeFor(34.2581, 44.7536, pr)