Fix the build, update of the README
This commit is contained in:
parent
3304395d55
commit
043a13badc
7 changed files with 174 additions and 86 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
|||
data/
|
||||
.idea/
|
||||
node_modules/
|
||||
lib/
|
||||
dist/
|
||||
.cache/
|
15
README.md
15
README.md
|
@ -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
122
client/countryCoder.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
11
package.json
11
package.json
|
@ -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
14
test.html
Normal 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
13
test.ts
Normal 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)
|
Loading…
Add table
Reference in a new issue