kopia lustrzana https://github.com/geodienst/lighthousemap
More precise parsing of seamark:light tags
rodzic
1655349f7a
commit
98d44fd133
26
index.html
26
index.html
|
@ -96,14 +96,28 @@
|
||||||
let lights = data.then(geojson => {
|
let lights = data.then(geojson => {
|
||||||
return L.indexedGeoJSON(null, {
|
return L.indexedGeoJSON(null, {
|
||||||
pointToLayer: function(feat, latlng) {
|
pointToLayer: function(feat, latlng) {
|
||||||
|
let sequence;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sequence = L.Light.sequence(feat.properties.tags, '#FF0');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing sequence: %s', e, feat.properties.tags);
|
||||||
|
|
||||||
|
// Fallback sequence
|
||||||
|
sequence = L.Light.sequence({
|
||||||
|
'seamark:light:sequence': '1+(1)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new L.Light(latlng, {
|
return new L.Light(latlng, {
|
||||||
interactive: false,
|
interactive: false,
|
||||||
title: feat.properties.tags['name'],
|
title: feat.properties.tags['name'],
|
||||||
radius: (parseFloat(feat.properties.tags['seamark:light:range'], 10) || 1) * 1000,
|
radius: (parseFloat(feat.properties.tags['seamark:light:range'], 10) || 1) * 1000,
|
||||||
sequence: new L.Light.Sequence(feat.properties.tags['seamark:light:sequence']),
|
sequence: sequence,
|
||||||
stroke: false,
|
stroke: false,
|
||||||
fillOpacity: 0.9,
|
fillOpacity: 0.9,
|
||||||
fillColor: '#FF0'
|
fill: !!sequence.state(0),
|
||||||
|
fillColor: sequence.state(0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).addTo(map).addData(geojson);
|
}).addTo(map).addData(geojson);
|
||||||
|
@ -112,11 +126,7 @@
|
||||||
lights.then(layer => {
|
lights.then(layer => {
|
||||||
let draw = function(t) {
|
let draw = function(t) {
|
||||||
layer.eachVisibleLayer(marker => {
|
layer.eachVisibleLayer(marker => {
|
||||||
try {
|
marker.setColor(marker.options.sequence.state(t));
|
||||||
marker.setState(marker.options.sequence.state(t));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e, marker);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -126,7 +136,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
update(0);
|
update(0);
|
||||||
})
|
}).catch(e => console.error(e));
|
||||||
|
|
||||||
lights.catch(e => console.error(e));
|
lights.catch(e => console.error(e));
|
||||||
|
|
||||||
|
|
167
leaflet.light.js
167
leaflet.light.js
|
@ -1,22 +1,159 @@
|
||||||
L.Light = L.Circle.extend({
|
L.Light = L.Circle.extend({
|
||||||
setState: function(state) {
|
setColor(color) {
|
||||||
if (this._state !== state) {
|
if (this._color !== color) {
|
||||||
L.Path.prototype.setStyle.call(this, {fill: !!state});
|
this._color = color;
|
||||||
this._state = state;
|
L.Path.prototype.setStyle.call(this, {fill: !!color, fillColor: color});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
L.Light.Sequence = class {
|
L.Light.sequence = function(tags, fallbackColor = '#FF0') {
|
||||||
constructor(seq) {
|
let character = tags['seamark:light:character'] || 'Fl';
|
||||||
this.setSequence(seq);
|
|
||||||
|
let colors = (tags['seamark:light:colour'] || fallbackColor).split(';');
|
||||||
|
|
||||||
|
let sequence = tags['seamark:light:sequence'];
|
||||||
|
|
||||||
|
if (character.match(/^Al\./)) {// Alternating color!
|
||||||
|
character = tags['seamark:light:character'].substring(3);
|
||||||
|
|
||||||
|
if (character == 'Iso' && sequence && sequence.match(/^\d+$/))
|
||||||
|
sequence = sequence + '+(' + sequence + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
setSequence(seq) {
|
if (character == 'Iso' && !sequence && 'seamark:light:period' in tags) {
|
||||||
|
const period = parseFloat(tags['seamark:light:period'], 10);
|
||||||
|
sequence = (period / 2) + '+(' + (period / 2) + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// For those Flashing lights that have a single number sequence
|
||||||
|
if (character.match(/^Fl|LFl|IQ$/) && sequence.match(/^\d+$/)) {
|
||||||
|
const flash = parseFloat(sequence)
|
||||||
|
const remainder = 'seamark:light:period' in tags ? (parseFloat(tags['seamark:light:period']) - flash) : flash;
|
||||||
|
character = 'Fl';
|
||||||
|
sequence = flash + '+(' + remainder + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert FFl to Fl
|
||||||
|
if (character == 'FFl' && sequence.match(/^\d+$/) && tags['seamark:light:period'].match(/^\d+$/)) {
|
||||||
|
character = 'Fl';
|
||||||
|
sequence = parseFloat(sequence, 10) + '+(' + (parseFloat(tags['seamark:light:period'], 10) - parseFloat(sequence, 10)) + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Q with Q+LFL sequence to Fl
|
||||||
|
if (character == 'Q' && 'seamark:light:period' in tags && sequence.match(/^Q(\(\d+\))?\s*\+\s*LFL/)) {
|
||||||
|
let qlfl = sequence.match(/^Q(\((\d+)\))?\s*\+\s*LFL/);
|
||||||
|
const period = parseFloat(tags['seamark:light:period']);
|
||||||
|
const short = parseFloat(qlfl[2] || tags['seamark:light:group'] || 1);
|
||||||
|
const long = 1;
|
||||||
|
const flash = 0.2;
|
||||||
|
const longflash = 1.0;
|
||||||
|
const remainder = period - (short * 2 * flash + longflash)
|
||||||
|
|
||||||
|
if (remainder < 0)
|
||||||
|
throw 'Could not convert Q+LFL to Fl: negative remainder';
|
||||||
|
|
||||||
|
character = 'Fl';
|
||||||
|
sequence = Array(short).fill(flash + '+(' + flash + ')').join('+') + '+' + longflash + '+(' + remainder + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert simple quick flashes which indicates how many with group and the total duration of that group with sequence into Fl.
|
||||||
|
if (character == 'Q' && sequence.match(/^\d$/) && 'seamark:light:group' in tags) {
|
||||||
|
const short = parseFloat(tags['seamark:light:group']);
|
||||||
|
const flash = parseFloat(sequence) / short / 2;
|
||||||
|
character = 'Fl';
|
||||||
|
sequence = Array(short).fill(flash + '+(' + flash + ')').join('+');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the 'second' suffix
|
||||||
|
sequence = sequence.replace(/s$/, '');
|
||||||
|
|
||||||
|
switch (character) {
|
||||||
|
case 'F': // Fixed Light
|
||||||
|
return new L.Light.Fixed(colors[0]);
|
||||||
|
|
||||||
|
case 'Iso':
|
||||||
|
return new L.Light.CombinedSequence(colors.map(color => {
|
||||||
|
return new L.Light.Sequence(sequence, color);
|
||||||
|
}));
|
||||||
|
|
||||||
|
case 'Oc': // Occulting Light
|
||||||
|
case 'Fl': // Flashing Light
|
||||||
|
case 'LFl': // Long Flash Light
|
||||||
|
case 'Q': // Quick Flashing Light
|
||||||
|
case 'Mo':
|
||||||
|
if (!sequence || sequence.match(/^\d+$/))
|
||||||
|
throw 'Unexpected sequence: ' + sequence;
|
||||||
|
|
||||||
|
const sequences = sequence.split(',').map((sequence, i) => {
|
||||||
|
let color = colors[i % colors.length];
|
||||||
|
|
||||||
|
// Does the sequence start with the color?
|
||||||
|
let letter = sequence.match(/^\[([A-Z]+)\.\](.+)$/);
|
||||||
|
if (letter) {
|
||||||
|
const expr = new RegExp('^' + letter[1], 'i');
|
||||||
|
color = colors.find(color => color.match(expr));
|
||||||
|
sequence = letter[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new L.Light.Sequence(sequence, color);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sequences.length < colors.length)
|
||||||
|
console.warn('There are fewer sequences than colors', {character, sequence, colors}, tags);
|
||||||
|
|
||||||
|
return new L.Light.CombinedSequence(sequences);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw 'Unknown character: ' + character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
L.Light.Fixed = class {
|
||||||
|
constructor(color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
state(time) {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
L.Light.CombinedSequence = class {
|
||||||
|
constructor(sequences) {
|
||||||
|
this.sequences = sequences;
|
||||||
|
|
||||||
|
this.sequences.forEach(sequence => {
|
||||||
|
sequence.offset = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.duration = this.sequences.reduce((sum, seq) => sum + seq.duration, 0);
|
||||||
|
|
||||||
|
this.offset = Math.random() * this.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
state(time) {
|
||||||
|
let dt = (this.offset + time) % this.duration;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (dt > this.sequences[i].duration) {
|
||||||
|
dt -= this.sequences[i++].duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sequences[i].state(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
L.Light.Sequence = class {
|
||||||
|
constructor(seq, color=true) {
|
||||||
|
this.setSequence(seq, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSequence(seq, color) {
|
||||||
this.text = seq;
|
this.text = seq;
|
||||||
|
|
||||||
this.steps = seq.split('+').map(step => {
|
this.steps = seq.replace(/\s/g, '').split('+').map(step => {
|
||||||
let state = true;
|
let state = color;
|
||||||
if (/^\(\d+(\.\d+)?\)$/.test(step)) {
|
if (/^\(\d+(\.\d+)?\)$/.test(step)) {
|
||||||
state = false;
|
state = false;
|
||||||
step = step.substring(1, step.length - 1);
|
step = step.substring(1, step.length - 1);
|
||||||
|
@ -26,17 +163,13 @@ L.Light.Sequence = class {
|
||||||
|
|
||||||
this.duration = this.steps.reduce((sum, step) => sum + step[1], 0);
|
this.duration = this.steps.reduce((sum, step) => sum + step[1], 0);
|
||||||
|
|
||||||
|
if (isNaN(this.duration))
|
||||||
|
throw 'Cannot parse sequence "' + this.text + '"';
|
||||||
|
|
||||||
this.offset = Math.random() * this.duration;
|
this.offset = Math.random() * this.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid() {
|
|
||||||
return this.steps.every(step => !isNaN(step[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
state(time) {
|
state(time) {
|
||||||
if (isNaN(this.duration))
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
let dt = (this.offset + time) % this.duration;
|
let dt = (this.offset + time) % this.duration;
|
||||||
|
|
||||||
for (let i = 0; i < this.steps.length; ++i) {
|
for (let i = 0; i < this.steps.length; ++i) {
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
{
|
||||||
|
"version": 0.6,
|
||||||
|
"generator": "Overpass API",
|
||||||
|
"osm3s": {
|
||||||
|
"timestamp_osm_base": "2017-08-31T18:02:02Z",
|
||||||
|
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 1191081664,
|
||||||
|
"lat": -41.1750000,
|
||||||
|
"lon": 146.4270000,
|
||||||
|
"tags": {
|
||||||
|
"seamark:information": "Occasional.",
|
||||||
|
"seamark:light:category": "aero",
|
||||||
|
"seamark:light:character": "Al.Fl",
|
||||||
|
"seamark:light:colour": "green;white",
|
||||||
|
"seamark:light:height": "23",
|
||||||
|
"seamark:light:period": "7.4",
|
||||||
|
"seamark:light:reference": "K 3563",
|
||||||
|
"seamark:light:sequence": "[W.]0.2+(3.5),[G.]0.2+(3.5)",
|
||||||
|
"seamark:type": "light_minor",
|
||||||
|
"source": "US NGA Pub. 111. 2010-09-02."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 1347582795,
|
||||||
|
"lat": 40.6590033,
|
||||||
|
"lon": 17.9417167,
|
||||||
|
"tags": {
|
||||||
|
"seamark:information": "Private light.",
|
||||||
|
"seamark:light:character": "Al.Fl",
|
||||||
|
"seamark:light:colour": "white;green",
|
||||||
|
"seamark:light:height": "18",
|
||||||
|
"seamark:light:period": "17",
|
||||||
|
"seamark:light:reference": "E 2208",
|
||||||
|
"seamark:light:sequence": "[W.]1+(3),[G.]1+(3),[W.]1+(8)",
|
||||||
|
"seamark:name": "Brindisi-Casale",
|
||||||
|
"seamark:type": "light_major",
|
||||||
|
"source": "US NGA Pub. 113. 2010-10-22."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 1581589349,
|
||||||
|
"lat": 32.4032167,
|
||||||
|
"lon": 34.8670833,
|
||||||
|
"tags": {
|
||||||
|
"seamark:light:character": "Al.Fl",
|
||||||
|
"seamark:light:colour": "white;red",
|
||||||
|
"seamark:light:height": "14",
|
||||||
|
"seamark:light:period": "15",
|
||||||
|
"seamark:light:range": "10",
|
||||||
|
"seamark:light:reference": "E 5956",
|
||||||
|
"seamark:light:sequence": "0.5+(4.5),0.5+(9.5)",
|
||||||
|
"seamark:name": "Mikhmoret",
|
||||||
|
"seamark:type": "light_minor",
|
||||||
|
"source": "US NGA Pub. 113. 2011-10-20."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 1581742946,
|
||||||
|
"lat": 34.2909833,
|
||||||
|
"lon": -6.6007500,
|
||||||
|
"tags": {
|
||||||
|
"seamark:information": "A F.R. light is shown from a radio tower, about 15.6 km. 194° .Occasional.",
|
||||||
|
"seamark:light:category": "aero",
|
||||||
|
"seamark:light:character": "Al.Fl",
|
||||||
|
"seamark:light:colour": "white;green",
|
||||||
|
"seamark:light:group": "2+1",
|
||||||
|
"seamark:light:height": "55",
|
||||||
|
"seamark:light:period": "10",
|
||||||
|
"seamark:light:reference": "D 2541",
|
||||||
|
"seamark:light:sequence": "1+(3.5)",
|
||||||
|
"seamark:name": "Kenitra",
|
||||||
|
"seamark:type": "light_major",
|
||||||
|
"source": "US NGA Pub. 113. 2011-10-20."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 2153785316,
|
||||||
|
"lat": 58.7372741,
|
||||||
|
"lon": 17.1029072,
|
||||||
|
"tags": {
|
||||||
|
"seamark:light:character": "Al.Iso",
|
||||||
|
"seamark:light:colour": "white;red",
|
||||||
|
"seamark:light:sequence": "3",
|
||||||
|
"seamark:type": "light_major"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 2153785362,
|
||||||
|
"lat": 58.7405845,
|
||||||
|
"lon": 17.0841161,
|
||||||
|
"tags": {
|
||||||
|
"seamark:light:character": "Iso",
|
||||||
|
"seamark:light:colour": "red",
|
||||||
|
"seamark:light:sequence": "3",
|
||||||
|
"seamark:name": "Linudden nedre",
|
||||||
|
"seamark:type": "light_major"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 2300566678,
|
||||||
|
"lat": 50.9707556,
|
||||||
|
"lon": 1.8400850,
|
||||||
|
"tags": {
|
||||||
|
"seamark:fog_signal:category": "bell",
|
||||||
|
"seamark:fog_signal:period": "5",
|
||||||
|
"seamark:light:character": "Iso",
|
||||||
|
"seamark:light:colour": "green",
|
||||||
|
"seamark:light:group": "2",
|
||||||
|
"seamark:light:height": "11.90",
|
||||||
|
"seamark:light:period": "3",
|
||||||
|
"seamark:light:range": "9",
|
||||||
|
"seamark:light:reference": "A 1148",
|
||||||
|
"seamark:light:sequence": "1.5+(1.5)",
|
||||||
|
"seamark:name": "Jetée Ouest",
|
||||||
|
"seamark:type": "light_minor",
|
||||||
|
"source": "US NGA Pub. 114. 2011-04-02",
|
||||||
|
"source:website": "http://www.pharesdumonde.fr/details.php?l=FRA&n=654&z=1&li=2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 3028106017,
|
||||||
|
"lat": 48.2790445,
|
||||||
|
"lon": -4.5885642,
|
||||||
|
"tags": {
|
||||||
|
"seamark:light:character": "Fl",
|
||||||
|
"seamark:light:colour": "green",
|
||||||
|
"seamark:light:group": "2",
|
||||||
|
"seamark:light:height": "9",
|
||||||
|
"seamark:light:period": "6",
|
||||||
|
"seamark:light:range": "5",
|
||||||
|
"seamark:light:sequence": "1+(1),1+(3)",
|
||||||
|
"seamark:type": "light_minor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"id": 3041544410,
|
||||||
|
"lat": 46.1482397,
|
||||||
|
"lon": -1.1697761,
|
||||||
|
"tags": {
|
||||||
|
"seamark:light:character": "Fl",
|
||||||
|
"seamark:light:colour": "green",
|
||||||
|
"seamark:light:group": "2",
|
||||||
|
"seamark:light:height": "9",
|
||||||
|
"seamark:light:period": "6",
|
||||||
|
"seamark:light:range": "5",
|
||||||
|
"seamark:light:reference": "D 1254",
|
||||||
|
"seamark:light:sequence": "1+(1),1+(3)",
|
||||||
|
"seamark:type": "light_minor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Ładowanie…
Reference in New Issue