first commit
This commit is contained in:
commit
148c0f264b
|
@ -0,0 +1,6 @@
|
|||
# OSM2IPFS
|
||||
|
||||
Starts from the world divided in 36 x 36 sections.
|
||||
Each division being from 0, 10, 20, to 360°
|
||||
|
||||
https://ipfs.asycn.io/ipfs/QmPcwC8tQRCNq1VvqsV7zLD6mMui5TgSmbu5suqiFR4Zfh/welcome.html
|
|
@ -0,0 +1,39 @@
|
|||
function selectExample(example) {
|
||||
$('#sphere').earth3d('destroy');
|
||||
$('#sphere').replaceWith($('<canvas id="sphere" width="400" height="400"></canvas>'));
|
||||
$('.location').remove();
|
||||
//~ $('.flight').remove();
|
||||
//~ $('#flights')[0].getContext('2d').clearRect(0, 0, 400, 400);
|
||||
if (example == 'simple_mars') {
|
||||
$('#glow-shadows').removeClass('earth').addClass('mars');
|
||||
} else {
|
||||
$('#glow-shadows').removeClass('mars').addClass('earth');
|
||||
}
|
||||
var code = examples[example].toString();
|
||||
code = code.substring(14);
|
||||
code = code.substring(0, code.length - 2);
|
||||
var lines = code.split("\n");
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
lines[i] = lines[i].substring(2);
|
||||
}
|
||||
code = lines.join("\n");
|
||||
$('#example_code').val(code);
|
||||
|
||||
examples[example]();
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
selectExample('locations');
|
||||
|
||||
$('#example').change(function() {
|
||||
selectExample($(this).val());
|
||||
});
|
||||
});
|
||||
|
||||
function addPath() {
|
||||
$('#sphere').earth3d('changePaths', {path2: {
|
||||
origin: 'obj1',
|
||||
destination: 'obj3'
|
||||
}});
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
body {
|
||||
background: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #DDD;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 10px;
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#demo {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#description {
|
||||
text-align: left;
|
||||
float: left;
|
||||
width: 49%;
|
||||
max-width: 500px;
|
||||
padding-top: 50px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
#showoff {
|
||||
float: left;
|
||||
width: 49%;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#sphere, #flights, #glow-shadows, #locations, #drag {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
#glow-shadows {
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.location {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
border: 2px solid white;
|
||||
margin-left: -5px;
|
||||
margin-top: -5px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.location:hover {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-left: -7.5px;
|
||||
margin-top: -7.5px;
|
||||
}
|
||||
|
||||
|
||||
.flight:hover {
|
||||
width: 36px;
|
||||
height: 37.5px;
|
||||
margin-left: -18px;
|
||||
margin-top: -18.75px;
|
||||
}
|
||||
|
||||
|
||||
.choose_example {
|
||||
width: 35%;
|
||||
margin-left: 32.5%;
|
||||
}
|
||||
|
||||
#example_code {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border: 0px;
|
||||
resize: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.social {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.social.twitter {
|
||||
vertical-align: -3px;
|
||||
}
|
||||
|
||||
.social.google {
|
||||
vertical-align: -7px;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 870 B |
|
@ -0,0 +1,136 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>OSM2IPFS : Choose your spot</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="jquery-ui.min.css">
|
||||
|
||||
<script type="text/javascript" src="requestanimationframe.polyfill.js"></script>
|
||||
|
||||
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="jquery-ui.0.min.js"></script>
|
||||
<script type="text/javascript" src="sphere-hacked.js"></script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="jquery.earth-3d.js"></script>
|
||||
|
||||
<script type="text/javascript" src="world.js"></script>
|
||||
|
||||
<script type="text/javascript" src="demo.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
examples['simple_mars'] = function() {
|
||||
$('#sphere').earth3d({
|
||||
texture: 'maps/mars1024x1024.jpg', // texture used by G1Wish planet
|
||||
dragElement: $('#locations') // where do we catch the mouse drag
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.slidecontainer {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gif-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gif-container img {
|
||||
margin: 10px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.slider #prev {
|
||||
float: left;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.slider #next {
|
||||
float: right;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
#glow-shadows.earth {
|
||||
background: url(maps/earth-glow-shadows.png);
|
||||
}
|
||||
|
||||
#glow-shadows.mars {
|
||||
background: url(maps/mars-glow-shadows.png);
|
||||
}
|
||||
|
||||
.flight {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 25px;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
background: url(maps/plain.png);
|
||||
background-size: 100% 100%;
|
||||
margin-left: -12px;
|
||||
margin-top: -12.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="earth.css">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>HELLO WORLD</h1>
|
||||
|
||||
<div id="container">
|
||||
<br><br>
|
||||
<br><br>
|
||||
<div id="sphere"></div>
|
||||
<div id="glow-shadows" class="earth"></div>
|
||||
<div id="flights"></div>
|
||||
<div id="locations"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
<div class="choose_example">
|
||||
Choose Planet: <select id="example">
|
||||
-->
|
||||
<!--
|
||||
<option value="simple">Simple earth</option>
|
||||
<option value="simple_tilted">Simple tilted earth</option>
|
||||
-->
|
||||
<!--
|
||||
<option value="simple_mars">Simple mars</option>
|
||||
-->
|
||||
<!--
|
||||
<option value="flights">Earth with locations and flights</option>
|
||||
-->
|
||||
<!--
|
||||
<option value="locations" selected >Ŋ1 Friends</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="code">
|
||||
<a href="#" onclick="$('#example_code').show(); $(this).hide().siblings('a').show(); return false;">Show code</a>
|
||||
<a href="#" style="display: none;" onclick="$('#example_code').hide(); $(this).hide().siblings('a').show(); return false;">Hide code</a>
|
||||
<textarea id="example_code" onclick="this.focus();this.select();"></textarea>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,851 @@
|
|||
|
||||
/*
|
||||
jquery.earth3d.js
|
||||
|
||||
jQuery ui plugin that allow you to draw a beautiful 3d spinning earth on canvas
|
||||
|
||||
Author: Sebastien Drouyer
|
||||
|
||||
Based on the amazing sphere.js plug of Sam Hasler
|
||||
|
||||
Licensed under the MIT license (MIT-LICENSE.txt)
|
||||
|
||||
http://sdrdis.github.com/jquery.earth-3d/
|
||||
|
||||
|
||||
|
||||
Depends:
|
||||
ui.core.js
|
||||
|
||||
|
||||
|
||||
|
||||
Options:
|
||||
* texture: texture map used by the planet
|
||||
|
||||
* sphere: rotation and size of the planet
|
||||
|
||||
* defaultSpeed: default spinning speed of the planet
|
||||
|
||||
* backToDefaultTime: time (in ms) to return by to default speed when planet is dragged
|
||||
|
||||
* locations: locations to display on the planet:
|
||||
* Each position must have a key, an alpha and delta position (or x and y if you want to display a static location).
|
||||
Any additional key can be reached via callbacks functions
|
||||
Example:
|
||||
{
|
||||
obj1: {
|
||||
alpha: Math.PI / 4,
|
||||
delta: 0,
|
||||
name: 'location 1'
|
||||
}
|
||||
}
|
||||
|
||||
* paths: paths and flights to display over the planet:
|
||||
Each path must have a key, an origin and a destination. The values are the location's key.
|
||||
You can, if you want to, define flights on these paths.
|
||||
Each flight has a key, a destination (the location's key) and a position.
|
||||
The position is the progress a fleet has made on its path.
|
||||
Any additional key can be reach via callbacks functions.
|
||||
Example:
|
||||
{
|
||||
path: {
|
||||
origin: 'obj1',
|
||||
destination: 'obj2',
|
||||
flights: {
|
||||
flight: {
|
||||
position: 0.25,
|
||||
destination: 'obj2',
|
||||
name: 'Flight 1'
|
||||
},
|
||||
flight2: {
|
||||
position: 0.25,
|
||||
destination: 'obj1',
|
||||
name: 'Flight 2'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
* flightsCanvas: Dom element which is a canvas and where the flights and paths are drawn
|
||||
|
||||
* dragElement: Dom element where we catch the mouse drag
|
||||
|
||||
* locationsElement: Dom elements where the locations are drawn
|
||||
|
||||
* flightsCanvasPosition: position of the flight canvas (can be use if you have some gap between your planet and your flights
|
||||
|
||||
* pixelRadiusMultiplier: (TEMPORARY) used by the getSphereRadiusInPixel (see the functions)
|
||||
|
||||
* onInitLocation: callback function which allows you to define what to do when the locations are initialized
|
||||
* Parameters:
|
||||
* location: location (coming from locations option)
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onShowLocation: callback function which allows you to define what to do when a location becomes visible (was behind the planet and is now in front of it)
|
||||
* Parameters:
|
||||
* location: location (coming from locations option)
|
||||
* x: 2d left position
|
||||
* y: 2d top position
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onRefreshLocation: callback function which allows you to define what to do when a location is refreshed (it moves)
|
||||
* Parameters:
|
||||
* location: location (coming from locations option)
|
||||
* x: 2d left position
|
||||
* y: 2d top position
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onHideLocation: callback function which allows you to define what to do when a location becomes invisible (was in front of the planet and is now behind it)
|
||||
* Parameters:
|
||||
* location: location (coming from locations option)
|
||||
* x: 2d left position
|
||||
* y: 2d top position
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onInitFlight: callback function which allows you to define what to do when the flights are initialized
|
||||
* Parameters:
|
||||
* flight: flight (coming from flights option)
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onShowFlight: callback function which allows you to define what to do when a flight becomes visible (was behind the planet and is now in front of it)
|
||||
* Parameters:
|
||||
* flight: flight (coming from flights option)
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onRefreshFlight: callback function which allows you to define what to do when a flight is refreshed (it moves)
|
||||
* Parameters:
|
||||
* flight: flight (coming from flights option)
|
||||
* x: 2d left position
|
||||
* y: 2d top position
|
||||
* widget: earth3d widget object
|
||||
|
||||
* onHideFlight: callback function which allows you to define what to do when a flight becomes invisible (was in front of the planet and is now behind it)
|
||||
* Parameters:
|
||||
* flight: flight (coming from flights option)
|
||||
* widget: earth3d widget object
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Functions
|
||||
|
||||
* getSphereRadiusInPixel: function which allows you to get the sphere radius in pixel
|
||||
/!| WARNING: this function needs to be refactored, since I didn't find out (my maths courses are far away) how to
|
||||
get the exact value. I did a basic linear regression, but it is not exact, and you will have to change the pixelRadiusMultiplier
|
||||
option to get the correct value
|
||||
|
||||
* destroy: use this function when you want to destroy the object. It will throw a cancel animation frame, so the
|
||||
CPU won't be used anymore.
|
||||
|
||||
* changePaths: use this function when you want to update paths and flights (options on widget)
|
||||
it will add the callback functions support
|
||||
|
||||
*/
|
||||
var earth3d;
|
||||
(function($) {
|
||||
$.widget('ui.earth3d', {
|
||||
options: {
|
||||
texture: 'maps/earth1024x1024.jpg',
|
||||
sphere: {
|
||||
tilt: 0,
|
||||
turn: 0,
|
||||
r: 10
|
||||
},
|
||||
defaultSpeed: 20,
|
||||
backToDefaultTime: 4000,
|
||||
locations: {
|
||||
},
|
||||
paths: {
|
||||
},
|
||||
flightsCanvas: null,
|
||||
dragElement: null,
|
||||
locationsElement: null,
|
||||
flightsCanvasPosition: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
tiling: {horizontal: 1, vertical: 1},
|
||||
pixelRadiusMultiplier: 0.97,
|
||||
onInitLocation: function(location, widget) {
|
||||
var $elem = $('<div class="location"></div>');
|
||||
$elem.appendTo(widget.options.locationsElement);
|
||||
$elem.click(function() {
|
||||
alert('Clicked on ' + location.name + ' : ' + location.link );
|
||||
window.open( location.link, "AstroTab");
|
||||
});
|
||||
location.$element = $elem;
|
||||
},
|
||||
onShowLocation: function(location, x, y) {
|
||||
location.$element.show();
|
||||
},
|
||||
onRefreshLocation: function(location, x, y) {
|
||||
//console.log(x, y);
|
||||
location.$element.css({
|
||||
left: x,
|
||||
top: y
|
||||
});
|
||||
},
|
||||
onHideLocation: function(location, x, y) {
|
||||
location.$element.hide();
|
||||
},
|
||||
onDeleteLocation: function(location) {
|
||||
location.$element.remove();
|
||||
},
|
||||
onInitFlight: function(flight, widget) {
|
||||
var $elem = $('<div class="flight"></div>');
|
||||
$elem.appendTo(widget.options.locationsElement);
|
||||
$elem.click(function() {
|
||||
alert('Clicked on ' + flight.name);
|
||||
|
||||
});
|
||||
flight.$element = $elem;
|
||||
},
|
||||
onShowFlight: function(flight) {
|
||||
flight.$element.show();
|
||||
},
|
||||
onRefreshFlight: function(flight, x, y, angle, widget) {
|
||||
flight.$element.css({
|
||||
left: x,
|
||||
top: y,
|
||||
'-webkit-transform':'rotate(' + ((angle + Math.PI / 2) * 360 / (2 * Math.PI)) + 'deg)',
|
||||
'-moz-transform':'rotate(' + ((angle + Math.PI / 2) * 360 / (2 * Math.PI)) + 'deg)',
|
||||
'-o-transform':'rotate(' + ((angle + Math.PI / 2) * 360 / (2 * Math.PI)) + 'deg)'
|
||||
});
|
||||
},
|
||||
onHideFlight: function(flight) {
|
||||
flight.$element.hide();
|
||||
},
|
||||
onDeleteFlight: function(flight) {
|
||||
flight.$element.remove();
|
||||
}
|
||||
},
|
||||
earth: null,
|
||||
posVar: 24 * 3600 * 1000,
|
||||
lastMousePos: null,
|
||||
lastSpeed: null,
|
||||
lastTime: null,
|
||||
lastTurnByTime: null,
|
||||
textureWidth: null,
|
||||
textureHeight: null,
|
||||
obj: null,
|
||||
flightsCtx: null,
|
||||
renderAnimationFrameId: null,
|
||||
mousePressed: null,
|
||||
|
||||
_create: function() {
|
||||
earth3d = this;
|
||||
var self = this;
|
||||
this.obj = $('div');
|
||||
if (this.options.flightsCanvas !== null) {
|
||||
this.flightsCtx = this.options.flightsCanvas[0].getContext('2d');
|
||||
}
|
||||
createSphere(this.element[0], this.options.texture, function(earth, textureWidth, textureHeight) { self._onSphereCreated(earth, textureWidth, textureHeight); }, this.options.tiling);
|
||||
if (this.options.dragElement !== null) {
|
||||
this.options.dragElement
|
||||
.bind('mousedown vmousedown', function(e) {
|
||||
self._mouseDragStart(e);
|
||||
self.mousePressed = true;
|
||||
})
|
||||
.bind('mouseup vmouseup', function(e) {
|
||||
self._mouseDragStop(e);
|
||||
self.mousePressed = false;
|
||||
})
|
||||
.bind('mousemove vmousemove', function(e){
|
||||
if (self.mousePressed) {
|
||||
self._mouseDrag(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
this._initLocations();
|
||||
this._initFlights();
|
||||
},
|
||||
|
||||
_initLocations: function() {
|
||||
for (var key in this.options.locations) {
|
||||
var location = this.options.locations[key];
|
||||
location.visible = true;
|
||||
this.options.onInitLocation(location, this);
|
||||
}
|
||||
},
|
||||
|
||||
_initFlights: function() {
|
||||
for (var key in this.options.paths) {
|
||||
var path = this.options.paths[key];
|
||||
for (var key in path.flights) {
|
||||
path.flights[key].visible = true;
|
||||
this.options.onInitFlight(path.flights[key], this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getSphereRadiusInPixel: function() {
|
||||
return this.earth.getRadius() / 2;
|
||||
},
|
||||
|
||||
_onSphereCreated: function(earth, textureWidth, textureHeight) {
|
||||
var self = this;
|
||||
this.textureWidth = textureWidth;
|
||||
this.textureHeight = textureHeight;
|
||||
this.earth = earth;
|
||||
this.earth.init(this.options.sphere);
|
||||
this.earth.turnBy = function(time) { return self._turnBy(time); };
|
||||
|
||||
var renderAnimationFrame = function(/* time */ time) {
|
||||
/* time ~= +new Date // the unix time */
|
||||
earth.renderFrame(time);
|
||||
self._renderAnimationFrame(time);
|
||||
self.renderAnimationFrameId = window.requestAnimationFrame(renderAnimationFrame);
|
||||
};
|
||||
this.renderAnimationFrameId = window.requestAnimationFrame(renderAnimationFrame);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
window.cancelAnimationFrame(this.renderAnimationFrameId);
|
||||
},
|
||||
|
||||
_renderAnimationFrame: function(time) {
|
||||
|
||||
|
||||
var ry=90+this.options.sphere.tilt;
|
||||
var rz=180+this.options.sphere.turn;
|
||||
|
||||
var RY = (90-ry);
|
||||
var RZ = (180-rz);
|
||||
var RX = 0,RY,RZ;
|
||||
|
||||
var rx=RX*Math.PI/180;
|
||||
var ry=RY*Math.PI/180;
|
||||
var rz=RZ*Math.PI/180;
|
||||
//console.log(rx, ry, rz);
|
||||
var r = this.getSphereRadiusInPixel();
|
||||
|
||||
var center = {
|
||||
x: this.element.width() / 2,
|
||||
y: this.element.height() / 2
|
||||
}
|
||||
|
||||
for (var key in this.options.locations) {
|
||||
var location = this.options.locations[key];
|
||||
|
||||
if (typeof location.delta === 'undefined') {
|
||||
location.flatPosition = {x: location.x, y: location.y};
|
||||
this.options.onRefreshLocation(location, location.x, location.y, this);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
WARNING: calculation of alphaAngle and deltaAngle is not exact
|
||||
I had to create the _calibrated functions to modify the deltaAngle to make the result look good on
|
||||
a spinning planet without rotation. It will totally bug with rotation!
|
||||
* */
|
||||
var progression = (((this.posVar + this.textureWidth * location.delta / (2 * Math.PI)) % this.textureWidth) / this.textureWidth);
|
||||
var alphaAngle = progression * 2 * Math.PI;
|
||||
var deltaAngle = this._calibrated(progression, location.alpha) * 2 * Math.PI;
|
||||
|
||||
|
||||
var objAlpha = ry + location.alpha - Math.sin(alphaAngle / 2) * 0.15 * (location.alpha - Math.PI / 2) / (Math.PI / 4);
|
||||
var objDelta = rz + deltaAngle;
|
||||
|
||||
var a = this._orbitalTo3d(objAlpha, objDelta, r);
|
||||
|
||||
var flatPosition = this._orthographicProjection(a);
|
||||
|
||||
if (a.x < 0 && !location.visible) {
|
||||
this.options.onShowLocation(location, flatPosition.x, flatPosition.y, this);
|
||||
}
|
||||
if (a.x > 0 && location.visible) {
|
||||
this.options.onHideLocation(location, flatPosition.x, flatPosition.y, this);
|
||||
}
|
||||
this.options.onRefreshLocation(location, flatPosition.x, flatPosition.y, this);
|
||||
|
||||
location.visible = a.x < 0;
|
||||
location.position = a;
|
||||
location.flatPosition = flatPosition;
|
||||
location.rAlpha = objAlpha;
|
||||
location.rDelta = objDelta;
|
||||
|
||||
}
|
||||
|
||||
if (this.flightsCtx !== null) {
|
||||
this.flightsCtx.clearRect(0, 0, this.options.flightsCanvas.width(), this.options.flightsCanvas.height());
|
||||
for (var key in this.options.paths) {
|
||||
this._drawPath(this.options.paths[key], center, r);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
_line_circle_intersection: function(A, B, C, r) {
|
||||
var d = {
|
||||
x: B.x - A.x,
|
||||
y: B.y - A.y
|
||||
};
|
||||
|
||||
var f = {
|
||||
x: A.x - C.x,
|
||||
y: A.y - C.y
|
||||
};
|
||||
|
||||
var a = this._dot(d, d);
|
||||
var b = 2 * this._dot(f, d);
|
||||
var c = this._dot(f, f) - r * r;
|
||||
|
||||
var discriminant = b * b - 4 * a * c;
|
||||
if (discriminant < 0) {
|
||||
return false;
|
||||
} else {
|
||||
discriminant = Math.sqrt(discriminant);
|
||||
var t1 = (-b + discriminant) / (2 * a);
|
||||
var t2 = (-b - discriminant) / (2 * a);
|
||||
|
||||
|
||||
var sols = [];
|
||||
|
||||
if (t1 >= 0 && t1 <= 1) {
|
||||
sols.push({
|
||||
x:A.x + t1 * d.x,
|
||||
y:A.y + t1 * d.y
|
||||
});
|
||||
}
|
||||
|
||||
if (t2 >= 0 && t2 <= 1) {
|
||||
sols.push({
|
||||
x:A.x + t2 * d.x,
|
||||
y:A.y + t2 * d.y
|
||||
});
|
||||
}
|
||||
|
||||
return sols;
|
||||
}
|
||||
},
|
||||
|
||||
_dot: function(A, B) {
|
||||
return A.x * B.x + A.y * B.y;
|
||||
},
|
||||
|
||||
_drawPath: function(path, center, r) {
|
||||
|
||||
|
||||
var originLocation = this.options.locations[path.origin];
|
||||
var destinationLocation = this.options.locations[path.destination];
|
||||
|
||||
var dotSize = 50;
|
||||
var spacing = 0.15;
|
||||
|
||||
if (typeof originLocation.delta === 'undefined' || typeof destinationLocation.delta === 'undefined') {
|
||||
var pathVisible = originLocation.visible && destinationLocation.visible;
|
||||
if (pathVisible) {
|
||||
|
||||
|
||||
var flatDistance = this._distance(originLocation.flatPosition, destinationLocation.flatPosition);
|
||||
|
||||
var nb = flatDistance * 0.9 / 20;
|
||||
// WARNING: we are drawing the paths on canvas, intensively using CPU. Could we gain by instead using SVG or the DOM ?
|
||||
for (var i = 0; i < nb; i++) {
|
||||
|
||||
|
||||
var fromFlatPosition = {
|
||||
x: ((nb - i) / nb) * originLocation.flatPosition.x + (i / nb) * destinationLocation.flatPosition.x,
|
||||
y: ((nb - i) / nb) * originLocation.flatPosition.y + (i / nb) * destinationLocation.flatPosition.y
|
||||
};
|
||||
|
||||
var toFlatPosition = {
|
||||
x: Math.max(((nb - (i + 1)) / nb), 0) * originLocation.flatPosition.x + Math.min(((i + 1) / nb), 1) * destinationLocation.flatPosition.x,
|
||||
y: Math.max(((nb - (i + 1)) / nb), 0) * originLocation.flatPosition.y + Math.min(((i + 1) / nb), 1) * destinationLocation.flatPosition.y
|
||||
};
|
||||
|
||||
var diff = {
|
||||
x: fromFlatPosition.x - toFlatPosition.x,
|
||||
y: fromFlatPosition.y - toFlatPosition.y,
|
||||
z: fromFlatPosition.z - toFlatPosition.z
|
||||
};
|
||||
|
||||
fromFlatPosition.x -= diff.x * spacing;
|
||||
fromFlatPosition.y -= diff.y * spacing;
|
||||
fromFlatPosition.z -= diff.z * spacing;
|
||||
toFlatPosition.x += diff.x * spacing;
|
||||
toFlatPosition.y += diff.y * spacing;
|
||||
toFlatPosition.z += diff.z * spacing;
|
||||
|
||||
|
||||
this.flightsCtx.lineWidth = 3;
|
||||
this.flightsCtx.beginPath();
|
||||
this.flightsCtx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
this.flightsCtx.moveTo(fromFlatPosition.x + this.options.flightsCanvasPosition.x, fromFlatPosition.y + this.options.flightsCanvasPosition.y);
|
||||
this.flightsCtx.lineTo(toFlatPosition.x + this.options.flightsCanvasPosition.x, toFlatPosition.y + this.options.flightsCanvasPosition.y);
|
||||
this.flightsCtx.stroke();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (var key in path.flights) {
|
||||
var flight = path.flights[key];
|
||||
|
||||
var position = flight.destination == path.destination ? flight.position : (1 - flight.position);
|
||||
|
||||
|
||||
var flightFlatPosition = {
|
||||
x: (1 - position) * originLocation.flatPosition.x + position * destinationLocation.flatPosition.x,
|
||||
y: (1 - position) * originLocation.flatPosition.y + position * destinationLocation.flatPosition.y
|
||||
};
|
||||
|
||||
if (!flight.visible && pathVisible) {
|
||||
this.options.onShowFlight(flight, this);
|
||||
flight.visible = true;
|
||||
}
|
||||
|
||||
if (flight.visible && !pathVisible) {
|
||||
this.options.onHideFlight(flight, this);
|
||||
flight.visible = false;
|
||||
}
|
||||
|
||||
var angle = Math.atan2(destinationLocation.flatPosition.y - originLocation.flatPosition.y, destinationLocation.flatPosition.x - originLocation.flatPosition.x) + (flight.destination == path.destination ? 0 : Math.PI);
|
||||
//console.log(flightAheadFlatPosition.y - flightFlatPosition.y);
|
||||
|
||||
this.options.onRefreshFlight(flight, flightFlatPosition.x, flightFlatPosition.y, angle, this);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var locationsDistance = this._distance(originLocation.position, destinationLocation.position);
|
||||
|
||||
|
||||
var middlePosition = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
|
||||
var radius = this._distance(originLocation.position, middlePosition);
|
||||
|
||||
var originP = {
|
||||
delta: Math.atan2((originLocation.position.y - middlePosition.y), (originLocation.position.x - middlePosition.x)),
|
||||
alpha: Math.acos((originLocation.position.z - middlePosition.z) / radius)
|
||||
};
|
||||
|
||||
var destinationP = {
|
||||
delta: Math.atan2((destinationLocation.position.y - middlePosition.y), (destinationLocation.position.x - middlePosition.x)),
|
||||
alpha: Math.acos((destinationLocation.position.z - middlePosition.z) / radius)
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (Math.abs(originP.delta - destinationP.delta) > Math.PI) {
|
||||
if ((originP.delta - destinationP.delta) > Math.PI) {
|
||||
originP.delta -= 2 * Math.PI;
|
||||
} else {
|
||||
originP.delta += 2 * Math.PI;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.sens) {
|
||||
if (((originP.delta - destinationP.delta) > 0 ? 1 : -1) != path.sens) {
|
||||
if (Math.abs(originP.delta - destinationP.delta) > Math.PI / 2) {
|
||||
originP.delta += path.sens * 2 * Math.PI;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
path.sens = (originP.delta - destinationP.delta) > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
|
||||
if (!path.nb) {
|
||||
path.nb = Math.round(((locationsDistance / (2 * r)) * Math.PI * 2 * r + (1 - (locationsDistance / (2 * r))) * locationsDistance) / dotSize);
|
||||
}
|
||||
var nb = path.nb;
|
||||
var maxDistance = 1.2;
|
||||
for (var i = 0; i < nb; i++) {
|
||||
var fromP = {
|
||||
alpha: ((nb - i) / nb) * originP.alpha + (i / nb) * destinationP.alpha,
|
||||
delta: ((nb - i) / nb) * originP.delta + (i / nb) * destinationP.delta
|
||||
};
|
||||
|
||||
var toP = {
|
||||
alpha: ((nb - 1 - i) / nb) * originP.alpha + ((i + 1) / nb) * destinationP.alpha,
|
||||
delta: ((nb - 1 - i) / nb) * originP.delta + ((i + 1) / nb) * destinationP.delta
|
||||
};
|
||||
//console.log(i, fromP.alpha, fromP.delta, toP.alpha, toP.delta);
|
||||
|
||||
var fromPosition = this._orbitalTo3d(fromP.alpha, fromP.delta, -(Math.sin(Math.PI * i / nb) * (maxDistance - 1) + 1) * radius);
|
||||
var toPosition = this._orbitalTo3d(toP.alpha, toP.delta, -(Math.sin(Math.PI * (i + 1) / nb) * (maxDistance - 1) + 1) * radius);
|
||||
var diff = {
|
||||
x: fromPosition.x - toPosition.x,
|
||||
y: fromPosition.y - toPosition.y,
|
||||
z: fromPosition.z - toPosition.z
|
||||
};
|
||||
|
||||
|
||||
|
||||
fromPosition.x -= diff.x * spacing;
|
||||
fromPosition.y -= diff.y * spacing;
|
||||
fromPosition.z -= diff.z * spacing;
|
||||
toPosition.x += diff.x * spacing;
|
||||
toPosition.y += diff.y * spacing;
|
||||
toPosition.z += diff.z * spacing;
|
||||
|
||||
|
||||
fromPosition.x += middlePosition.x;
|
||||
fromPosition.y += middlePosition.y;
|
||||
fromPosition.z += middlePosition.z;
|
||||
toPosition.x += middlePosition.x;
|
||||
toPosition.y += middlePosition.y;
|
||||
toPosition.z += middlePosition.z;
|
||||
|
||||
|
||||
|
||||
var fromFlatPosition = this._orthographicProjection(fromPosition);
|
||||
var toFlatPosition = this._orthographicProjection(toPosition);
|
||||
|
||||
var fromDistanceCenter = this._distance(fromFlatPosition, center);
|
||||
var toDistanceCenter = this._distance(toFlatPosition, center);
|
||||
|
||||
var fromVisible = true;
|
||||
var toVisible = true;
|
||||
if (fromPosition.x > 0) {
|
||||
if (fromDistanceCenter <= r) {
|
||||
fromVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (toPosition.x > 0) {
|
||||
if (toDistanceCenter <= r) {
|
||||
toVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(i, fromVisible, toVisible);
|
||||
|
||||
if (!fromVisible && !toVisible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fromVisible) {
|
||||
var intersection = this._line_circle_intersection(fromFlatPosition, toFlatPosition, center, r);
|
||||
if (intersection.length == 0) {
|
||||
continue;
|
||||
}
|
||||
fromFlatPosition = intersection[0];
|
||||
}
|
||||
|
||||
if (!toVisible) {
|
||||
var intersection = this._line_circle_intersection(fromFlatPosition, toFlatPosition, center, r);
|
||||
if (intersection.length == 0) {
|
||||
continue;
|
||||
}
|
||||
toFlatPosition = intersection[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.flightsCtx.lineWidth = 3;
|
||||
this.flightsCtx.beginPath();
|
||||
this.flightsCtx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
this.flightsCtx.moveTo(fromFlatPosition.x + this.options.flightsCanvasPosition.x, fromFlatPosition.y + this.options.flightsCanvasPosition.y);
|
||||
this.flightsCtx.lineTo(toFlatPosition.x + this.options.flightsCanvasPosition.x, toFlatPosition.y + this.options.flightsCanvasPosition.y);
|
||||
this.flightsCtx.stroke();
|
||||
|
||||
}
|
||||
|
||||
for (var key in path.flights) {
|
||||
var flight = path.flights[key];
|
||||
|
||||
var position = flight.destination == path.destination ? flight.position : (1 - flight.position);
|
||||
var positionAhead = flight.destination == path.destination ? (flight.position + 0.01) : (1 - (flight.position + 0.01));
|
||||
|
||||
var flightP = {
|
||||
alpha: (1 - position) * originP.alpha + position * destinationP.alpha,
|
||||
delta: (1 - position) * originP.delta + position * destinationP.delta
|
||||
};
|
||||
|
||||
var flightAheadP = {
|
||||
alpha: (1 - positionAhead) * originP.alpha + positionAhead * destinationP.alpha,
|
||||
delta: (1 - positionAhead) * originP.delta + positionAhead * destinationP.delta
|
||||
};
|
||||
|
||||
var flightPosition = this._orbitalTo3d(flightP.alpha, flightP.delta, -(Math.sin(Math.PI * position) * (maxDistance - 1) + 1) * radius);
|
||||
var flightAheadPosition = this._orbitalTo3d(flightAheadP.alpha, flightAheadP.delta, -(Math.sin(Math.PI * positionAhead) * (maxDistance - 1) + 1) * radius);
|
||||
|
||||
flightPosition.x += middlePosition.x;
|
||||
flightPosition.y += middlePosition.y;
|
||||
flightPosition.z += middlePosition.z;
|
||||
flightAheadPosition.x += middlePosition.x;
|
||||
flightAheadPosition.y += middlePosition.y;
|
||||
flightAheadPosition.z += middlePosition.z;
|
||||
|
||||
var flightFlatPosition = this._orthographicProjection(flightPosition);
|
||||
var flightAheadFlatPosition = this._orthographicProjection(flightAheadPosition);
|
||||
|
||||
var flightDistanceCenter = this._distance(flightFlatPosition, center);
|
||||
|
||||
if (!flight.visible && (flightPosition.x < 0 || flightDistanceCenter > r)) {
|
||||
this.options.onShowFlight(flight, this);
|
||||
flight.visible = true;
|
||||
}
|
||||
|
||||
if (flight.visible && (flightPosition.x > 0 && flightDistanceCenter < r)) {
|
||||
this.options.onHideFlight(flight, this);
|
||||
flight.visible = false;
|
||||
}
|
||||
|
||||
var angle = Math.atan2(flightAheadFlatPosition.y - flightFlatPosition.y, flightAheadFlatPosition.x - flightFlatPosition.x);
|
||||
//console.log(flightAheadFlatPosition.y - flightFlatPosition.y);
|
||||
|
||||
this.options.onRefreshFlight(flight, flightFlatPosition.x, flightFlatPosition.y, angle, this);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_distance: function(A, B) {
|
||||
if (A.z) {
|
||||
return Math.sqrt(
|
||||
(A.x - B.x) * (A.x - B.x) +
|
||||
(A.y - B.y) * (A.y - B.y) +
|
||||
(A.z - B.z) * (A.z - B.z)
|
||||
);
|
||||
} else {
|
||||
return Math.sqrt(
|
||||
(A.x - B.x) * (A.x - B.x) +
|
||||
(A.y - B.y) * (A.y - B.y)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// WARNING: temporary function to make the locations look good on a spinning planet without rotation
|
||||
_calibrated: function(x, alpha) {
|
||||
var calib = 0.3 + 0.15 * Math.abs(alpha - Math.PI / 2) / (Math.PI / 4);
|
||||
//console.log(calib);
|
||||
var y = calib * (4 * (x - 0.5) * (x - 0.5) * (x - 0.5) + 0.5) + (1 - calib) * x;
|
||||
return y;
|
||||
},
|
||||
|
||||
|
||||
/* WARNING:
|
||||
Obviously there is something wrong with _orbitalTo3d and _orthographicProjection, since
|
||||
I can't get a descent display of locations when the planet is rotated. That's why I had to create the _calibrated
|
||||
function in the first place. I didn't have time to look precisely into it, and I probably don't know enough math.
|
||||
|
||||
I leaved the _3dProjection function I found on wikipedia but is not working. (I might not have correctly understood / write it)
|
||||
*/
|
||||
_orbitalTo3d: function(alpha, delta, r) {
|
||||
return {
|
||||
x: -r * Math.sin(alpha) * Math.cos(delta),
|
||||
y: -r * Math.sin(alpha) * Math.sin(delta),
|
||||
z: -r * Math.cos(alpha)
|
||||
};
|
||||
},
|
||||
|
||||
_orthographicProjection: function(position) {
|
||||
return {x: position.y + this.element.width() / 2, y: position.z + this.element.height() / 2};
|
||||
},
|
||||
|
||||
_3dProjection: function(a, c, delta, e) {
|
||||
// Wikipedia is your friend :) : http://en.wikipedia.org/wiki/3D_projection
|
||||
var d = {x: 0, y: 0, z: 0};
|
||||
d.x = Math.cos(delta.y) * (Math.sin(delta.z) * (a.y - c.y) + Math.cos(delta.z) * (a.x - c.x)) - Math.sin(delta.y) * (a.z - c.z);
|
||||
d.y = Math.sin(delta.x) * (Math.cos(delta.y) * (a.z - c.z) + Math.sin(delta.y) * (Math.sin(delta.z) * (a.y - c.y) + Math.cos(delta.z) * (a.x - c.x)))
|
||||
+ Math.cos(delta.x) * (Math.cos(delta.z) * (a.y - c.y) - Math.sin(delta.z) * (a.x - c.x))
|
||||
d.z = Math.cos(delta.x) * (Math.cos(delta.y) * (a.z - c.z) + Math.sin(delta.y) * (Math.sin(delta.z) * (a.y - c.y) + Math.cos(delta.z) * (a.x - c.x)))
|
||||
- Math.sin(delta.x) * (Math.cos(delta.z) * (a.y - c.y) - Math.sin(delta.z) * (a.x - c.x));
|
||||
|
||||
return {
|
||||
x: d.z, //(d.x - e.x) * (e.y / d.y),
|
||||
y: d.y //(d.z - e.z) * (e.y / d.y)
|
||||
};
|
||||
},
|
||||
|
||||
_mouseDragStart: function(e) {
|
||||
this.lastMousePos = e.clientX;
|
||||
this.lastSpeed = null;
|
||||
},
|
||||
|
||||
_mouseDrag: function(e) {
|
||||
this.lastSpeed = (e.clientX - this.lastMousePos);
|
||||
this.posVar = this.posVar - this.lastSpeed;
|
||||
this.lastMousePos = e.clientX;
|
||||
},
|
||||
_mouseDragStop: function(e) {
|
||||
this.lastMousePos = null;
|
||||
this.lastTime = null;
|
||||
},
|
||||
|
||||
_turnBy: function(time) {
|
||||
if (this.lastTurnByTime === null) {
|
||||
this.lastTurnByTime = time;
|
||||
}
|
||||
var timeDiff = (time - this.lastTurnByTime) / 1000;
|
||||
if (this.lastMousePos === null) {
|
||||
if (this.lastSpeed !== null) {
|
||||
if (this.lastTime === null) {
|
||||
this.lastTime = time;
|
||||
}
|
||||
if (this.options.backToDefaultTime + this.lastTime - time < 0) {
|
||||
this.lastSpeed = null;
|
||||
} else {
|
||||
var backToDef = (this.options.backToDefaultTime + this.lastTime - time) / this.options.backToDefaultTime;
|
||||
this.posVar -= this.lastSpeed * backToDef + (this.options.defaultSpeed * timeDiff) * (1 - backToDef);
|
||||
}
|
||||
} else {
|
||||
this.posVar -= this.options.defaultSpeed * timeDiff;
|
||||
}
|
||||
}
|
||||
this.lastTurnByTime = time;
|
||||
return this.posVar;
|
||||
},
|
||||
|
||||
_getQBezierValue: function (t, p1, p2, p3) {
|
||||
var iT = 1 - t;
|
||||
return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
|
||||
},
|
||||
|
||||
_getQBezierDerivation: function(t, p1, p2, p3) {
|
||||
return (2 * p1 - 4 * p2 + 2 * p3) * t + 2 * p2 - 2 * p1;
|
||||
},
|
||||
|
||||
_getQBezierAngle: function(startX, startY, cpX, cpY, endX, endY, position) {
|
||||
var x = this._getQBezierDerivation(position, startX, cpX, endX);
|
||||
var y = this._getQBezierDerivation(position, startY, cpY, endY);
|
||||
return Math.atan2(y, x);
|
||||
},
|
||||
|
||||
_getQuadraticCurvePoint: function(startX, startY, cpX, cpY, endX, endY, position) {
|
||||
return {
|
||||
x: this._getQBezierValue(position, startX, cpX, endX),
|
||||
y: this._getQBezierValue(position, startY, cpY, endY),
|
||||
angle: this._getQBezierAngle(startX, startY, cpX, cpY, endX, endY, position)
|
||||
};
|
||||
},
|
||||
|
||||
changeLocations: function(locations) {
|
||||
for (var key in this.options.locations) {
|
||||
this.options.onDeleteLocation(this.options.locations[key], this);
|
||||
}
|
||||
this.options.locations = locations;
|
||||
this._initLocations();
|
||||
},
|
||||
|
||||
changePaths: function(paths) {
|
||||
for (var key in this.options.paths) {
|
||||
var path = this.options.paths[key];
|
||||
for (var keyFlight in path.flights) {
|
||||
var flight = path.flights[keyFlight];
|
||||
this.options.onDeleteFlight(flight, this);
|
||||
}
|
||||
}
|
||||
this.options.paths = paths;
|
||||
this._initFlights();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})($);
|
||||
|
|
@ -0,0 +1,640 @@
|
|||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Prevents IE11 from highlighting tiles in blue */
|
||||
.leaflet-tile::selection {
|
||||
background: transparent;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg,
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer,
|
||||
.leaflet-container .leaflet-tile {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
/* Fallback for FF which doesn't support pinch-zoom */
|
||||
touch-action: none;
|
||||
touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-tile {
|
||||
will-change: opacity;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive,
|
||||
svg.leaflet-image-layer.leaflet-interactive path {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline: 0;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-container a.leaflet-active {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path {
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-container .leaflet-control-attribution,
|
||||
.leaflet-container .leaflet-control-scale {
|
||||
font-size: 11px;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 19px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 4px 4px 0 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||
color: #c3c3c3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||
color: #999;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip-container {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-clickable {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Leaflet Map</title>
|
||||
<link rel="stylesheet" href="leaflet.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" style="width: 800px; height: 600px;"></div>
|
||||
|
||||
<br>
|
||||
|
||||
<script src="leaflet.js"></script>
|
||||
<script>
|
||||
// const desiredImageWidthInKm = 11; // Now Is calculated from northEastLon - southWestLon
|
||||
const tileSizeInPixels = 256;
|
||||
|
||||
function calculateZoomLevel(desiredImageWidthInKm, latitude) {
|
||||
const earthEquatorialCircumference = 40075016.686; // Earth's equatorial circumference in meters
|
||||
// Calculate the distance covered by one degree of longitude at the given latitude
|
||||
const metersPerLongitudeDegree = earthEquatorialCircumference * Math.cos((Math.PI / 180) * latitude) / 360;
|
||||
const metersPerPixel = metersPerLongitudeDegree * 360 / (tileSizeInPixels * Math.pow(2, 20));
|
||||
// Calculate the number of pixels needed to achieve the desired width
|
||||
const numberOfPixelsHorizontally = (desiredImageWidthInKm * 1000) / metersPerPixel;
|
||||
// Calculate the appropriate zoom level
|
||||
const zoomLevel = Math.log2(tileSizeInPixels * Math.pow(2, 20) / numberOfPixelsHorizontally);
|
||||
return zoomLevel;
|
||||
}
|
||||
|
||||
// Function to extract URL parameters
|
||||
function getUrlParameter(name) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||
const results = regex.exec(location.search);
|
||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
const defaultSouthWestLat = 44.000;
|
||||
const defaultSouthWestLon = -1.000;
|
||||
const defaultOffsetDegrees = 10.000; // 10° offset
|
||||
|
||||
const southWestLat = parseFloat(getUrlParameter('southWestLat')) || defaultSouthWestLat;
|
||||
console.log('Lat:', southWestLat);
|
||||
const southWestLon = parseFloat(getUrlParameter('southWestLon')) || defaultSouthWestLon;
|
||||
console.log('Lon:', southWestLon);
|
||||
|
||||
const deg = parseFloat(getUrlParameter('deg')) || defaultOffsetDegrees;
|
||||
console.log('Offset:', deg);
|
||||
|
||||
// Calculate northEastLat and northEastLon with deg° offset only if parameters are empty
|
||||
const northEastLatParam = getUrlParameter('northEastLat');
|
||||
const northEastLonParam = getUrlParameter('northEastLon');
|
||||
|
||||
const northEastLat = northEastLatParam ? parseFloat(northEastLatParam) : southWestLat + deg;
|
||||
const northEastLon = northEastLonParam ? parseFloat(northEastLonParam) : southWestLon + deg;
|
||||
|
||||
// Calculate the longitudinal distance in degrees
|
||||
const lonDistanceInDegrees = Math.abs(northEastLon - southWestLon);
|
||||
console.log('lonDistanceInDegrees:', lonDistanceInDegrees);
|
||||
|
||||
// Calculate the desired image width in kilometers
|
||||
const earthEquatorialCircumference = 40075016.686; // Earth's equatorial circumference in meters
|
||||
const metersPerLongitudeDegree = Math.abs(earthEquatorialCircumference * Math.cos((Math.PI / 180) * southWestLat) / 360);
|
||||
const desiredImageWidthInMeters = lonDistanceInDegrees * metersPerLongitudeDegree;
|
||||
const desiredImageWidthInKm = desiredImageWidthInMeters / 1000;
|
||||
console.log('desiredImageWidthInKm:', desiredImageWidthInKm);
|
||||
|
||||
const centerLat = (southWestLat + northEastLat) / 2;
|
||||
const centerLon = (southWestLon + northEastLon) / 2;
|
||||
|
||||
// Provide the latitude for adjustment
|
||||
const latitude = centerLat;
|
||||
const zoomLevel = calculateZoomLevel(desiredImageWidthInKm, latitude);
|
||||
console.log('Recommended zoom level:', zoomLevel);
|
||||
|
||||
const map = L.map('map').setView([centerLat, centerLon], zoomLevel);
|
||||
|
||||
// Use the Mapbox Satellite tile layer
|
||||
//~ L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/{z}/{x}/{y}?access_token=YOUR_MAPBOX_ACCESS_TOKEN', {
|
||||
//~ attribution: '© Mapbox',
|
||||
//~ tileSize: 512,
|
||||
//~ zoomOffset: -1,
|
||||
//~ accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN'
|
||||
//~ }).addTo(map);
|
||||
|
||||
//~ // Use the OpenAerialMap (OAM) tile layer for satellite view
|
||||
//~ L.tileLayer('https://tiles.openaerialmap.org/5d6d1e370f437a00106e06ef/0/{z}/{x}/{y}.jpg', {
|
||||
//~ attribution: '© OpenAerialMap',
|
||||
//~ maxZoom: 18
|
||||
//~ }).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: 'U Planet'
|
||||
}).addTo(map);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 305 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,30 @@
|
|||
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
|
||||
|
||||
// requestAnimationFrame polyfill by Erik Möller
|
||||
// fixes from Paul Irish and Tino Zijdel
|
||||
|
||||
(function() {
|
||||
var lastTime = 0;
|
||||
var vendors = ['ms', 'moz', 'webkit', 'o'];
|
||||
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
|
||||
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|
||||
|| window[vendors[x]+'CancelRequestAnimationFrame'];
|
||||
}
|
||||
|
||||
if (!window.requestAnimationFrame)
|
||||
window.requestAnimationFrame = function(callback, element) {
|
||||
var currTime = new Date().getTime();
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
||||
timeToCall);
|
||||
lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
};
|
||||
|
||||
if (!window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame = function(id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,608 @@
|
|||
// **Sphere** renders a mathematically perfect textured sphere.
|
||||
// It calculates the surface of the sphere instead of approximating it with triangles.
|
||||
// Shamefully hacked by Sébastien Drouyer
|
||||
|
||||
/*jshint laxcomma: true, laxbreak: true, browser: true */
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
var opts = { tilt: 40
|
||||
, turn: 20
|
||||
};
|
||||
|
||||
// Tiling informations
|
||||
var tiling = {
|
||||
horizontal: 1,
|
||||
vertical: 1
|
||||
};
|
||||
|
||||
// frame count, current angle of rotation. inc/dec to turn.
|
||||
var gCtx;
|
||||
var gImage, gCtxImg;
|
||||
|
||||
//Variable to hold the size of the canvas
|
||||
var size;
|
||||
|
||||
var canvasImageData, textureImageData;
|
||||
|
||||
// Constants for indexing dimentions
|
||||
var X=0, Y=1, Z=2;
|
||||
|
||||
var textureWidth, textureHeight;
|
||||
|
||||
var hs=30; // Horizontal scale of viewing area
|
||||
var vs=30; // Vertical scale of viewing area
|
||||
|
||||
// NB The viewing area is an abstract rectangle in the 3d world and is not
|
||||
// the same as the canvas used to display the image.
|
||||
|
||||
var F = [0,0,0]; // Focal point of viewer
|
||||
var S = [0,30,0]; // Centre of sphere/planet
|
||||
|
||||
var r=12; // Radius of sphere/planet
|
||||
|
||||
// Distance of the viewing area from the focal point. This seems
|
||||
// to give strange results if it is not equal to S[Y]. It should
|
||||
// theoreticaly be changable but hs & vs can still be used along
|
||||
// with r to change how large the sphere apears on the canvas.
|
||||
// HOWEVER, the values of hs, vs, S[Y], f & r MUST NOT BE TOO BIG
|
||||
// as this will result in overflow errors which are not traped
|
||||
// and do not stop the script but will result in incorrect
|
||||
// displaying of the texture upon the sphere.
|
||||
var f = 30;
|
||||
|
||||
|
||||
// There may be a solution to the above problem by finding L in
|
||||
// a slightly different way.
|
||||
// Since the problem is equivelent to finding the intersection
|
||||
// in 2D space of a line and a circle then each view area pixel
|
||||
// and associated vector can be used define a 2D plane in the 3D
|
||||
// space that 'contains' the vector S-F which is the focal point
|
||||
// to centre of the sphere.
|
||||
//
|
||||
// This is essentialy the same problem but I belive/hope it will
|
||||
// not result in the same exact solution. I have hunch that the
|
||||
// math will not result in such big numbers. Since this abstract
|
||||
// plane will be spinning, it may be posilbe to use the symetry
|
||||
// of the arangement to reuse 1/4 of the calculations.
|
||||
|
||||
|
||||
|
||||
// Variables to hold rotations about the 3 axis
|
||||
var RX = 0,RY,RZ;
|
||||
// Temp variables to hold them whilst rendering so they won't get updated.
|
||||
var rx,ry,rz;
|
||||
|
||||
var a;
|
||||
var b;
|
||||
var b2; // b squared
|
||||
var bx=F[X]-S[X]; // = 0 for current values of F and S
|
||||
var by=F[Y]-S[Y];
|
||||
var bz=F[Z]-S[Z]; // = 0 for current values of F and S
|
||||
|
||||
// c = Fx^2 + Sx^2 -2FxSx + Fy^2 + Sy^2 -2FySy + Fz^2 + Sz^2 -2FzSz - r^2
|
||||
// for current F and S this means c = Sy^2 - r^2
|
||||
|
||||
var c = F[X]*F[X] + S[X]*S[X]
|
||||
+ F[Y]*F[Y] + S[Y]*S[Y]
|
||||
+ F[Z]*F[Z] + S[Z]*S[Z]
|
||||
- 2*(F[X]*S[X] + F[Y]*S[Y] + F[Z]*S[Z])
|
||||
- r*r
|
||||
;
|
||||
|
||||
var c4 = c*4; // save a bit of time maybe during rendering
|
||||
|
||||
var s;
|
||||
|
||||
var m1 = 0;
|
||||
//double m2 = 0;
|
||||
|
||||
// The following are use to calculate the vector of the current pixel to be
|
||||
// drawn from the focus position F
|
||||
|
||||
var hs_ch; // horizontal scale divided by canvas width
|
||||
var vs_cv; // vertical scale divided by canvas height
|
||||
var hhs = 0.5*hs; // half horizontal scale
|
||||
var hvs = 0.5*vs; // half vertical scale
|
||||
|
||||
var V = new Array(3); // vector for storing direction of each pixel from F
|
||||
var L = new Array(3); // Location vector from S that pixel 'hits' sphere
|
||||
|
||||
var VY2=f*f; // V[Y] ^2 NB May change if F changes
|
||||
|
||||
|
||||
var rotCache = {};
|
||||
|
||||
|
||||
var calculateVector = function(h,v) {
|
||||
|
||||
// Calculate vector from focus point (Origin, so can ignor) to pixel
|
||||
V[X]=(hs_ch*h)-hhs;
|
||||
|
||||
// V[Y] always the same as view frame doesn't mov
|
||||
V[Z]=(vs_cv*v)-hvs;
|
||||
|
||||
// Vector (L) from S where m*V (m is an unknown scalar) intersects
|
||||
// surface of sphere is as follows
|
||||
//
|
||||
// <pre>
|
||||
// L = F + mV - S
|
||||
//
|
||||
// ,-------.
|
||||
// / \ -----m------
|
||||
// | S<-L->| <-V->F
|
||||
// \ /
|
||||
// `-------'
|
||||
//
|
||||
// L and m are unknown so find magnitude of vectors as the magnitude
|
||||
// of L is the radius of the sphere
|
||||
//
|
||||
// |L| = |F + mV - S| = r
|
||||
//
|
||||
// Can be rearranged to form a quadratic
|
||||
//
|
||||
// 0 = am² +bm + c
|
||||
//
|
||||
// and solved to find m, using the following formula
|
||||
//
|
||||
// <pre>
|
||||
// ___________
|
||||
// m = ( -b ± \/(b²) - 4ac ) /2a
|
||||
// </pre>
|
||||
//
|
||||
// r = |F + mV - S|
|
||||
// __________________________________________________
|
||||
// r = v(Fx + mVx -Sx)² + (Fy + mVy -Sy)² + (Fz + mVz -Sz)²
|
||||
//
|
||||
// r² = (Fx + mVx -Sx)² + (Fy + mVy -Sy)² + (Fz + mVz -Sz)²
|
||||
//
|
||||
// r² = (Fx + mVx -Sx)² + (Fy + mVy -Sy)² + (Fz + mVz -Sz)²
|
||||
//
|
||||
// 0 = Fx² + FxVxm -FxSx + FxVxm + Vx²m² -SxVxm -SxFx -SxVxm + Sx²
|
||||
// +Fy² + FyVym -FySy + FyVym + Vy²m² -SyVym -SyFy -SyVym + Sy²
|
||||
// +Fz² + FzVzm -FzSz + FzVzm + Vz²m² -SzVzm -SzFz -SzVzm + Sz² - r²
|
||||
//
|
||||
// 0 = Vx²m² + FxVxm + FxVxm -2SxVxm + Fx² -FxSx -SxFx + Sx²
|
||||
// +Vy²m² + FyVym + FyVym -2SyVym + Fy² -FySy -SyFy + Sy²
|
||||
// +Vz²m² + FzVzm + FzVzm -2SzVzm + Fz² -FzSz -SzFz + Sz² - r²
|
||||
//
|
||||
// 0 = (Vx² + Vy² + Vz²)m² + (FxVx + FxVx -2SxVx)m + Fx² - 2FxSx + Sx²
|
||||
// + (FyVy + FyVy -2SyVy)m + Fy² - 2FySy + Sy²
|
||||
// + (FzVz + FzVz -2SzVz)m + Fz² - 2FzSz + Sz² - r²
|
||||
//
|
||||
// 0 = |Vz|m² + (FxVx + FxVx -2SxVx)m + |F| - 2FxSx + |S|
|
||||
// + (FyVy + FyVy -2SyVy)m - 2FySy
|
||||
// + (FyVy + FyVy -2SyVy)m - 2FySy - r²
|
||||
//
|
||||
// a = |Vz|
|
||||
// b =
|
||||
// c = Fx² + Sx² -2FxSx + Fy² + Sy² -2FySy + Fz² + Sz² -2FzSz - r²
|
||||
// for current F and S this means c = Sy² - r²
|
||||
// </pre>
|
||||
|
||||
// Where a, b and c are as in the code.
|
||||
// Only the solution for the negative square root term is needed as the
|
||||
// closest intersection is wanted. The other solution to m would give
|
||||
// the intersection of the 'back' of the sphere.
|
||||
|
||||
a=V[X]*V[X]+VY2+V[Z]*V[Z];
|
||||
|
||||
|
||||
s=(b2-a*c4); // the square root term
|
||||
|
||||
// if s is negative then there are no solutions to m and the
|
||||
// sphere is not visible on the current pixel on the canvas
|
||||
// so only draw a pixel if the sphere is visable
|
||||
// 0 is a special case as it is the 'edge' of the sphere as there
|
||||
// is only one solution. (I have never seen it happen though)
|
||||
// of the two solutions m1 & m2 the nearest is m1, m2 being the
|
||||
// far side of the sphere.
|
||||
|
||||
if (s > 0) {
|
||||
|
||||
m1 = ((-b)-(Math.sqrt(s)))/(2*a);
|
||||
|
||||
L[X]=m1*V[X]; // bx+m1*V[X];
|
||||
L[Y]=by+(m1*V[Y]);
|
||||
L[Z]=m1*V[Z]; // bz+m1*V[Z];
|
||||
|
||||
// Do a couple of rotations on L
|
||||
|
||||
var lx=L[X];
|
||||
var srz = Math.sin(rz);
|
||||
var crz = Math.cos(rz);
|
||||
L[X]=lx*crz-L[Y]*srz;
|
||||
L[Y]=lx*srz+L[Y]*crz;
|
||||
|
||||
var lz;
|
||||
lz=L[Z];
|
||||
var sry = Math.sin(ry);
|
||||
var cry = Math.cos(ry);
|
||||
L[Z]=lz*cry-L[Y]*sry;
|
||||
L[Y]=lz*sry+L[Y]*cry;
|
||||
|
||||
|
||||
// Calculate the position that this location on the sphere
|
||||
// coresponds to on the texture
|
||||
|
||||
var lh = textureWidth + textureWidth * ( Math.atan2(L[Y],L[X]) + Math.PI ) / (2*Math.PI);
|
||||
|
||||
// %textureHeight at end to get rid of south pole bug. probaly means that one
|
||||
// pixel may be a color from the opposite pole but as long as the
|
||||
// poles are the same color this won't be noticed.
|
||||
|
||||
var lv = textureWidth * Math.floor(textureHeight-1-(textureHeight*(Math.acos(L[Z]/r)/Math.PI)%textureHeight));
|
||||
return {lv:lv,lh:lh};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the sphere function opject
|
||||
*/
|
||||
var sphere = function(){
|
||||
|
||||
var textureData = textureImageData.data;
|
||||
var canvasData = canvasImageData.data;
|
||||
|
||||
var copyFnc;
|
||||
|
||||
if (canvasData.splice){
|
||||
//2012-04-19 splice on canvas data not supported in any current browser
|
||||
copyFnc = function(idxC, idxT){
|
||||
canvasData.splice(idxC, 4 , textureData[idxT + 0]
|
||||
, textureData[idxT + 1]
|
||||
, textureData[idxT + 2]
|
||||
, 255);
|
||||
};
|
||||
} else {
|
||||
copyFnc = function(idxC, idxT){
|
||||
canvasData[idxC + 0] = textureData[idxT + 0];
|
||||
canvasData[idxC + 1] = textureData[idxT + 1];
|
||||
canvasData[idxC + 2] = textureData[idxT + 2];
|
||||
canvasData[idxC + 3] = 255;
|
||||
};
|
||||
}
|
||||
|
||||
var getVector = (function(){
|
||||
var cache = new Array(size*size);
|
||||
return function(pixel){
|
||||
if (cache[pixel] === undefined){
|
||||
var v = Math.floor(pixel / size);
|
||||
var h = pixel - v * size;
|
||||
cache[pixel] = calculateVector(h,v);
|
||||
}
|
||||
return cache[pixel];
|
||||
};
|
||||
})();
|
||||
|
||||
var posDelta = textureWidth*0.2/(20*1000);
|
||||
//var firstFramePos = (new Date()) * posDelta;
|
||||
|
||||
var stats = {fastCount: 0, fastSumMs: 0};
|
||||
|
||||
return {
|
||||
posDelta: posDelta,
|
||||
firstFramePos: (new Date()) * posDelta,
|
||||
positionsCache: [],
|
||||
minX: null,
|
||||
minY: null,
|
||||
maxX: null,
|
||||
maxY: null,
|
||||
|
||||
init: function(options) {
|
||||
this.changeRotation(options);
|
||||
|
||||
|
||||
hs=30; // Horizontal scale of viewing area
|
||||
vs=30; // Vertical scale of viewing area
|
||||
|
||||
F = [0,0,0]; // Focal point of viewer
|
||||
S = [0,30,0]; // Centre of sphere/planet
|
||||
|
||||
r=options.r; // Radius of sphere/planet
|
||||
|
||||
|
||||
f = 30;
|
||||
|
||||
|
||||
|
||||
bx=F[X]-S[X]; // = 0 for current values of F and S
|
||||
by=F[Y]-S[Y];
|
||||
bz=F[Z]-S[Z]; // = 0 for current values of F and S
|
||||
|
||||
c = F[X]*F[X] + S[X]*S[X]
|
||||
+ F[Y]*F[Y] + S[Y]*S[Y]
|
||||
+ F[Z]*F[Z] + S[Z]*S[Z]
|
||||
- 2*(F[X]*S[X] + F[Y]*S[Y] + F[Z]*S[Z])
|
||||
- r*r
|
||||
;
|
||||
|
||||
c4 = c*4; // save a bit of time maybe during rendering
|
||||
|
||||
m1 = 0;
|
||||
|
||||
hhs = 0.5*hs; // half horizontal scale
|
||||
hvs = 0.5*vs; // half vertical scale
|
||||
|
||||
/*V = new Array(3);*/ // vector for storing direction of each pixel from F
|
||||
L = new Array(3); // Location vector from S that pixel 'hits' sphere
|
||||
|
||||
VY2=f*f; // V[Y] ^2 NB May change if F changes
|
||||
|
||||
|
||||
|
||||
rotCache = {};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (canvasData.splice){
|
||||
//2012-04-19 splice on canvas data not supported in any current browser
|
||||
copyFnc = function(idxC, idxT){
|
||||
canvasData.splice(idxC, 4 , textureData[idxT + 0]
|
||||
, textureData[idxT + 1]
|
||||
, textureData[idxT + 2]
|
||||
, 255);
|
||||
};
|
||||
} else {
|
||||
copyFnc = function(idxC, idxT){
|
||||
canvasData[idxC + 0] = textureData[idxT + 0];
|
||||
canvasData[idxC + 1] = textureData[idxT + 1];
|
||||
canvasData[idxC + 2] = textureData[idxT + 2];
|
||||
canvasData[idxC + 3] = 255;
|
||||
};
|
||||
}
|
||||
|
||||
posDelta = textureWidth*0.2/(20*1000);
|
||||
//var firstFramePos = (new Date()) * posDelta;
|
||||
|
||||
stats = {fastCount: 0, fastSumMs: 0};
|
||||
|
||||
getVector = (function(){
|
||||
var cache = new Array(size*size);
|
||||
return function(pixel){
|
||||
if (cache[pixel] === undefined){
|
||||
var v = Math.floor(pixel / size);
|
||||
var h = pixel - v * size;
|
||||
cache[pixel] = calculateVector(h,v);
|
||||
}
|
||||
return cache[pixel];
|
||||
};
|
||||
})();
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
renderFrame: function(time){
|
||||
this.RF(time);
|
||||
return;
|
||||
stats.firstMs = new Date() - time;
|
||||
this.renderFrame = this.sumRF;
|
||||
console.log(rotCache);
|
||||
for (var key in rotCache){
|
||||
if (rotCache[key] > 1){
|
||||
console.log(rotCache[key]);
|
||||
}
|
||||
}
|
||||
},
|
||||
sumRF: function(time){
|
||||
this.RF(time);
|
||||
stats.fastSumMs += new Date() - time;
|
||||
stats.fastCount++;
|
||||
if (stats.fastSumMs > stats.firstMs) {
|
||||
// alert("calc:precompute ratio = 1:"+ stats.fastCount +" "+ stats.fastSumMs +" "+ stats.firstMs);
|
||||
this.renderFrame = this.RF;
|
||||
}
|
||||
},
|
||||
|
||||
turnBy: function(time){
|
||||
return 24*60*60 + this.firstFramePos - time * this.posDelta
|
||||
},
|
||||
|
||||
changeRotation: function(opts) {
|
||||
ry=90+opts.tilt;
|
||||
rz=180+opts.turn;
|
||||
|
||||
RY = (90-ry);
|
||||
RZ = (180-rz);
|
||||
RX = 0,RY,RZ;
|
||||
},
|
||||
|
||||
getRadius: function() {
|
||||
if (this.minX === null) {
|
||||
return null;
|
||||
} else {
|
||||
return ((this.maxX - this.minX) + (this.maxY - this.minY)) / 2;
|
||||
}
|
||||
},
|
||||
|
||||
getTexturePointPosition: function(x, y) {
|
||||
var maxDistance = 30;
|
||||
for (var i = 0; i < maxDistance; i++) {
|
||||
var xx
|
||||
var yy;
|
||||
var pos;
|
||||
for (xx = x - i; xx < x + i + 1; xx++) {
|
||||
yy = y - i;
|
||||
pos = this.getTexturePointPositionExact(xx, yy);
|
||||
if (typeof pos !== 'undefined') {
|
||||
return pos;
|
||||
}
|
||||
yy = y + i;
|
||||
pos = this.getTexturePointPositionExact(xx, yy);
|
||||
if (typeof pos !== 'undefined') {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
for (yy = y - i + 1; yy < y + i; yy++) {
|
||||
xx = x - i;
|
||||
pos = this.getTexturePointPositionExact(xx, yy);
|
||||
if (typeof pos !== 'undefined') {
|
||||
return pos;
|
||||
}
|
||||
xx = x + i;
|
||||
pos = this.getTexturePointPositionExact(xx, yy);
|
||||
if (typeof pos !== 'undefined') {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getTexturePointPositionExact: function(x, y) {
|
||||
var pixel = this.positionsCache[x + y * textureWidth];
|
||||
if (typeof pixel === 'undefined') {
|
||||
return pixel;
|
||||
} else {
|
||||
return {x: pixel % size, y: Math.floor(pixel / size), pixel: pixel, originalX: x, originalY: y};
|
||||
}
|
||||
},
|
||||
|
||||
RF: function(time){
|
||||
// RX, RY & RZ may change part way through if the newR? (change tilt/turn) meathods are called while
|
||||
// this meathod is running so put them in temp vars at render start.
|
||||
// They also need converting from degrees to radians
|
||||
rx=RX*Math.PI/180;
|
||||
ry=RY*Math.PI/180;
|
||||
rz=RZ*Math.PI/180;
|
||||
|
||||
// add to 24*60*60 so it will be a day before turnBy is negative and it hits the slow negative modulo bug
|
||||
var turnBy = this.turnBy(time);
|
||||
var pixel = size*size;
|
||||
var h2 = (textureHeight * textureHeight);
|
||||
|
||||
this.positionsCache = new Array(h2);
|
||||
|
||||
this.minX = null;
|
||||
this.minY = null;
|
||||
this.maxX = null;
|
||||
this.maxY = null;
|
||||
|
||||
while(pixel--){
|
||||
var vector = getVector(pixel);
|
||||
if (vector !== null){
|
||||
var x = pixel % size;
|
||||
var y = Math.floor(pixel / size);
|
||||
if (this.minX == null) {
|
||||
this.minX = x;
|
||||
this.maxX = x;
|
||||
this.minY = y;
|
||||
this.maxY = y;
|
||||
} else {
|
||||
if (this.minX > x) {
|
||||
this.minX = x;
|
||||
}
|
||||
if (this.maxX < x) {
|
||||
this.maxX = x;
|
||||
}
|
||||
if (this.minY > y) {
|
||||
this.minY = y;
|
||||
}
|
||||
if (this.maxY < y) {
|
||||
this.maxY = y;
|
||||
}
|
||||
}
|
||||
//rotate texture on sphere
|
||||
var lh = Math.floor(vector.lh * tiling.horizontal + turnBy * tiling.horizontal) % textureWidth;
|
||||
/* lh = (lh < 0)
|
||||
? ((textureWidth-1) - ((lh-1)%textureWidth))
|
||||
: (lh % textureWidth) ;
|
||||
*/
|
||||
var idxC = pixel * 4;
|
||||
var idxT = ((lh + (vector.lv * tiling.vertical) % h2) * 4);
|
||||
this.positionsCache[Math.floor(idxT / 4)] = Math.floor(idxC / 4);
|
||||
|
||||
/* TODO light for alpha channel or alter s or l in hsl color value?
|
||||
- fn to calc distance between two points on sphere?
|
||||
- attenuate light by distance from point and rotate point separate from texture rotation
|
||||
*/
|
||||
|
||||
// Update the values of the pixel;
|
||||
canvasData[idxC + 0] = textureData[idxT + 0];
|
||||
canvasData[idxC + 1] = textureData[idxT + 1];
|
||||
canvasData[idxC + 2] = textureData[idxT + 2];
|
||||
canvasData[idxC + 3] = 255;
|
||||
|
||||
// Slower?
|
||||
/*
|
||||
canvasImageData.data[idxC + 0] = textureImageData.data[idxT + 0];
|
||||
canvasImageData.data[idxC + 1] = textureImageData.data[idxT + 1];
|
||||
canvasImageData.data[idxC + 2] = textureImageData.data[idxT + 2];
|
||||
canvasImageData.data[idxC + 3] = 255;
|
||||
*/
|
||||
// Faster?
|
||||
/* copyFnc(idxC,idxT); */
|
||||
}
|
||||
}
|
||||
gCtx.putImageData(canvasImageData, 0, 0);
|
||||
}};
|
||||
};
|
||||
|
||||
function copyImageToBuffer(aImg)
|
||||
{
|
||||
gImage = document.createElement('canvas');
|
||||
textureWidth = aImg.naturalWidth;
|
||||
textureHeight = aImg.naturalHeight;
|
||||
gImage.width = textureWidth;
|
||||
gImage.height = textureHeight;
|
||||
|
||||
gCtxImg = gImage.getContext("2d");
|
||||
gCtxImg.clearRect(0, 0, textureHeight, textureWidth);
|
||||
gCtxImg.drawImage(aImg, 0, 0);
|
||||
textureImageData = gCtxImg.getImageData(0, 0, textureHeight, textureWidth);
|
||||
|
||||
hs_ch = (hs / size);
|
||||
vs_cv = (vs / size);
|
||||
}
|
||||
|
||||
this.createSphere = function (gCanvas, textureUrl, callback, tilingInfos) {
|
||||
size = Math.min(gCanvas.width, gCanvas.height);
|
||||
gCtx = gCanvas.getContext("2d");
|
||||
canvasImageData = gCtx.createImageData(size, size);
|
||||
tiling = tilingInfos;
|
||||
|
||||
hs_ch = (hs / size);
|
||||
vs_cv = (vs / size);
|
||||
|
||||
V[Y]=f;
|
||||
|
||||
b=(2*(-f*V[Y]));
|
||||
b2=Math.pow(b,2);
|
||||
|
||||
var img = new Image();
|
||||
|
||||
img.onload = function() {
|
||||
|
||||
copyImageToBuffer(img);
|
||||
var earth = sphere();
|
||||
callback(earth, textureWidth, textureHeight);
|
||||
|
||||
|
||||
// BAD! uses 100% CPU, stats.js runs at 38FPS
|
||||
/*
|
||||
function renderFrame(){
|
||||
earth.renderFrame(new Date);
|
||||
}
|
||||
setInterval(renderFrame, 0);
|
||||
*/
|
||||
// Better - runs at steady state
|
||||
/*
|
||||
(function loop(){
|
||||
setTimeout(function(){
|
||||
earth.renderFrame(new Date);
|
||||
loop();
|
||||
}, 0);
|
||||
})();
|
||||
*/
|
||||
// Best! only renders frames that will be seen. stats.js runs at 60FPS on my desktop
|
||||
|
||||
|
||||
};
|
||||
img.setAttribute("src", textureUrl);
|
||||
};
|
||||
}).call(this);
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="leaflet.css">
|
||||
<style>
|
||||
.clickable-area {
|
||||
fill-opacity: 0.2; /* Adjust the opacity as needed */
|
||||
fill: #0074d9; /* Adjust the color as needed */
|
||||
stroke: #001f3f;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="leaflet.js"></script>
|
||||
<title>Welcome to Planet Earth</title>
|
||||
<style>
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<script src="welcome.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
// Set up initial variables
|
||||
const tileSize = 10; // 10° interval
|
||||
const gridSize = 36;
|
||||
const initialZoom = 1; // Choose an initial zoom level
|
||||
|
||||
// Create the map
|
||||
const map = L.map('map').setView([0, 0], initialZoom);
|
||||
|
||||
// Add OpenStreetMap tile layer
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: 'U Planet'
|
||||
}).addTo(map);
|
||||
|
||||
// Create a clickable area for each grid cell
|
||||
for (let latIndex = 0; latIndex < gridSize; latIndex++) {
|
||||
for (let lonIndex = 0; lonIndex < gridSize; lonIndex++) {
|
||||
const lat = -180 + latIndex * tileSize;
|
||||
const lon = -180 + lonIndex * tileSize;
|
||||
|
||||
// Create a clickable rectangle
|
||||
const rectangle = L.rectangle(
|
||||
[[lat, lon], [lat + tileSize, lon + tileSize]],
|
||||
{ color: 'transparent', weight: 1, className: 'clickable-area' } // Add CSS class
|
||||
);
|
||||
|
||||
// Add a click event to the rectangle
|
||||
rectangle.on('click', () => {
|
||||
const url = `map_render.html?southWestLat=${lat}&southWestLon=${lon}`;
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
// Add the rectangle to the map
|
||||
rectangle.addTo(map);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
var examples = {};
|
||||
|
||||
//~ examples['simple'] = function() {
|
||||
//~ $('#sphere').earth3d({
|
||||
//~ dragElement: $('#locations') // where do we catch the mouse drag
|
||||
//~ });
|
||||
//~ };
|
||||
|
||||
//~ examples['simple_tilted'] = function() {
|
||||
//~ $('#sphere').earth3d({
|
||||
//~ dragElement: $('#locations'), // where do we catch the mouse drag
|
||||
//~ sphere: { // rotation and size of the planet
|
||||
//~ tilt: 40,
|
||||
//~ turn: 20,
|
||||
//~ r: 10
|
||||
//~ }
|
||||
//~ });
|
||||
//~ };
|
||||
|
||||
|
||||
examples['locations'] = function() {
|
||||
/* defining locations to display.
|
||||
Each position must have a key, an alpha and delta position (or x and y if you want to display a static location).
|
||||
Any additional key can be reached via callbacks functions.
|
||||
*/
|
||||
var locations = {};
|
||||
|
||||
for (let latIndex = 0; latIndex < 36; latIndex++) {
|
||||
for (let lonIndex = 0; lonIndex < 36; lonIndex++) {
|
||||
const alpha = (90 - lonIndex * 10) * (Math.PI / 180);
|
||||
const delta = (latIndex * 10) * (Math.PI / 180);
|
||||
|
||||
const southWestLat = 90 - latIndex * 10;
|
||||
const southWestLon = lonIndex * 10;
|
||||
|
||||
const objKey = `obj_${latIndex}_${lonIndex}`;
|
||||
const objName = `_${latIndex}_${lonIndex}_`;
|
||||
const objLink = `map_render.html?southWestLat=${southWestLat}&southWestLon=${southWestLon}`;
|
||||
|
||||
locations[objKey] = {
|
||||
alpha: alpha,
|
||||
delta: delta,
|
||||
name: objName,
|
||||
link: objLink
|
||||
};
|
||||
}
|
||||
}
|
||||
//~ var locations = {
|
||||
//~ obj1: {
|
||||
//~ alpha: Math.PI / 4,
|
||||
//~ delta: 0,
|
||||
//~ name: '_usa_',
|
||||
//~ link: 'https://oasis.astroport.com#usa'
|
||||
//~ },
|
||||
//~ obj2: {
|
||||
//~ alpha: 1 * Math.PI / 4,
|
||||
//~ delta: -2 * Math.PI / 4,
|
||||
//~ name: '_africa_',
|
||||
//~ link: 'https://oasis.astroport.com#africa'
|
||||
//~ },
|
||||
//~ obj3: {
|
||||
//~ alpha: 2 * Math.PI / 4,
|
||||
//~ delta: 0,
|
||||
//~ name: '_hawai_',
|
||||
//~ link: 'https://oasis.astroport.com#awai'
|
||||
//~ },
|
||||
//~ obj4: {
|
||||
//~ alpha: 3 * Math.PI / 4,
|
||||
//~ delta: 3 * Math.PI / 4,
|
||||
//~ name: '_australia_',
|
||||
//~ link: 'https://oasis.astroport.com#australia'
|
||||
//~ },
|
||||
//~ obj5: {
|
||||
//~ alpha: 2.2 * Math.PI / 4,
|
||||
//~ delta: -0.9 * Math.PI / 4,
|
||||
//~ name: '_southamerica_',
|
||||
//~ link: 'https://oasis.astroport.com#southamerica'
|
||||
//~ },
|
||||
//~ obj6: {
|
||||
//~ alpha: 1.2 * Math.PI / 4,
|
||||
//~ delta: -2 * Math.PI / 4,
|
||||
//~ name: '_europe_',
|
||||
//~ link: 'https://oasis.astroport.com#europe'
|
||||
//~ }
|
||||
//~ };
|
||||
|
||||
$('#sphere').earth3d({
|
||||
locationsElement: $('#locations'),
|
||||
dragElement: $('#locations'), // where do we catch the mouse drag
|
||||
locations: locations
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//~ examples['flights'] = function() {
|
||||
//~ /* defining locations to display.
|
||||
//~ Each position must have a key, an alpha and delta position (or x and y if you want to display a static location).
|
||||
//~ Any additional key can be reached via callbacks functions.
|
||||
//~ */
|
||||
//~ var locations = {
|
||||
//~ obj1: {
|
||||
//~ alpha: Math.PI / 4,
|
||||
//~ delta: 0,
|
||||
//~ name: '_usa_',
|
||||
//~ link: 'https://oasis.astroport.com#usa'
|
||||
//~ },
|
||||
//~ obj2: {
|
||||
//~ alpha: 1 * Math.PI / 4,
|
||||
//~ delta: -2 * Math.PI / 4,
|
||||
//~ name: '_africa_',
|
||||
//~ link: 'https://oasis.astroport.com#africa'
|
||||
//~ },
|
||||
//~ obj3: {
|
||||
//~ alpha: 2 * Math.PI / 4,
|
||||
//~ delta: 0,
|
||||
//~ name: '_hawai_',
|
||||
//~ link: 'https://oasis.astroport.com#awai'
|
||||
//~ },
|
||||
//~ obj4: {
|
||||
//~ alpha: 3 * Math.PI / 4,
|
||||
//~ delta: 3 * Math.PI / 4,
|
||||
//~ name: '_australia_',
|
||||
//~ link: 'https://oasis.astroport.com#australia'
|
||||
//~ },
|
||||
//~ obj5: {
|
||||
//~ alpha: 2.2 * Math.PI / 4,
|
||||
//~ delta: -0.9 * Math.PI / 4,
|
||||
//~ name: '_southamerica_',
|
||||
//~ link: 'https://oasis.astroport.com#southamerica'
|
||||
//~ },
|
||||
//~ obj6: {
|
||||
//~ alpha: 1.2 * Math.PI / 4,
|
||||
//~ delta: -2 * Math.PI / 4,
|
||||
//~ name: '_europe_',
|
||||
//~ link: 'https://oasis.astroport.com#europe'
|
||||
//~ },
|
||||
//~ zero: {
|
||||
//~ alpha: 0 * Math.PI / 4,
|
||||
//~ delta: 0 * Math.PI / 4,
|
||||
//~ name: '_CraftYourWorld_',
|
||||
//~ link: 'https://ipfs.copylaradio.com/ipfs/QmNcNcYRDUFmR1Ey1MAyhzzZRJEi1Dfq8YXRTXq6XZ9n4A'
|
||||
//~ },
|
||||
//~ pi: {
|
||||
//~ alpha: -3 * Math.PI / 4,
|
||||
//~ delta: -3 * Math.PI / 4,
|
||||
//~ name: '_OpenTW_',
|
||||
//~ link: 'https://astroport.copylaradio.com'
|
||||
//~ }
|
||||
//~ };
|
||||
//~ /* defining paths to display.
|
||||
//~ Each path must have a key, an origin and a destination. The values are the location's key.
|
||||
//~ You can, if you want to, define flights on these paths.
|
||||
//~ Each flight has a key, a destination (the location's key) and a position.
|
||||
//~ The position is the progress a fleet has made on its path.
|
||||
//~ Any additional key can be reach via callbacks functions.
|
||||
//~ */
|
||||
//~ var paths = {
|
||||
//~ path: {
|
||||
//~ origin: 'obj1',
|
||||
//~ destination: 'obj2',
|
||||
//~ flights: {
|
||||
//~ flight: {
|
||||
//~ position: 0.25,
|
||||
//~ destination: 'obj2',
|
||||
//~ name: 'Flight 1'
|
||||
//~ },
|
||||
//~ flight2: {
|
||||
//~ position: 0.25,
|
||||
//~ destination: 'obj1',
|
||||
//~ name: 'Flight 2'
|
||||
//~ }
|
||||
//~ }
|
||||
//~ },
|
||||
//~ path2: {
|
||||
//~ origin: 'obj1',
|
||||
//~ destination: 'obj3',
|
||||
//~ flights: {
|
||||
//~ flight3: {
|
||||
//~ position: 0.5,
|
||||
//~ destination: 'obj3',
|
||||
//~ name: 'Flight 3'
|
||||
//~ }
|
||||
//~ }
|
||||
//~ },
|
||||
//~ path3: {
|
||||
//~ origin: 'obj1',
|
||||
//~ destination: 'obj4',
|
||||
//~ flights: {
|
||||
//~ flight4: {
|
||||
//~ position: 0.5,
|
||||
//~ destination: 'obj4',
|
||||
//~ name: 'Flight 4'
|
||||
//~ }
|
||||
//~ }
|
||||
//~ },
|
||||
//~ path4: {
|
||||
//~ origin: 'obj1',
|
||||
//~ destination: 'obj5'
|
||||
//~ },
|
||||
//~ path7: {
|
||||
//~ origin: 'obj1',
|
||||
//~ destination: 'obj5',
|
||||
//~ flights: {
|
||||
//~ flight5: {
|
||||
//~ position: 0.25,
|
||||
//~ destination: 'obj7',
|
||||
//~ name: 'Flight 5'
|
||||
//~ }
|
||||
//~ }
|
||||
//~ }
|
||||
//~ }
|
||||
$('#sphere').earth3d({
|
||||
dragElement: $('#locations'), // where do we catch the mouse drag
|
||||
sphere: { // rotation and size of the planet
|
||||
tilt: 40,
|
||||
turn: 20,
|
||||
r: 100
|
||||
}
|
||||
});
|
||||
//~ $('#sphere').earth3d({
|
||||
//~ flightsCanvas: $('#flights'),
|
||||
//~ locationsElement: $('#locations'),
|
||||
//~ dragElement: $('#locations'), // where do we catch the mouse drag
|
||||
//~ paths: paths,
|
||||
//~ locations: locations
|
||||
//~ });
|
||||
//~ };
|
|
@ -0,0 +1,41 @@
|
|||
const puppeteer = require('puppeteer');
|
||||
|
||||
(async () => {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
executablePath: '/usr/bin/chromium'
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
const zoomLevel = 13;
|
||||
const tilesAroundCenter = 2; // Adjust as needed
|
||||
|
||||
const tileSize = 360 / Math.pow(2, zoomLevel);
|
||||
const tileCount = (2 * tilesAroundCenter) + 1;
|
||||
|
||||
const tileIncrement = tileSize * tileCount;
|
||||
|
||||
const centerLat = 51.000;
|
||||
const centerLon = 0.000;
|
||||
|
||||
for (let latOffset = -tilesAroundCenter; latOffset <= tilesAroundCenter; latOffset++) {
|
||||
for (let lonOffset = -tilesAroundCenter; lonOffset <= tilesAroundCenter; lonOffset++) {
|
||||
const tileLat = centerLat + latOffset * tileIncrement;
|
||||
const tileLon = centerLon + lonOffset * tileIncrement;
|
||||
|
||||
//~ http://ipfs.asycn.io/ipfs/QmZzE8ypb2q9CF93B8gbjif3Qpqhsrh8y5DtgPPwGYTpdP/map_render.html?southWestLat=${tileLat}&southWestLon=${tileLon}°=1
|
||||
|
||||
const url = `file:///tmp/osm/index.html?lat=${tileLat}&lon=${tileLon}&zoom=${zoomLevel}`;
|
||||
|
||||
await page.goto(url);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const fileName = `map_${tileLat.toFixed(6)}_${tileLon.toFixed(6)}.png`;
|
||||
|
||||
await page.screenshot({ path: fileName });
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
})();
|
|
@ -0,0 +1,92 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Leaflet Map</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" style="width: 800px; height: 600px;"></div>
|
||||
|
||||
<br>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
<script>
|
||||
// const desiredImageWidthInKm = 11; // Now Is calculated from northEastLon - southWestLon
|
||||
const tileSizeInPixels = 256;
|
||||
|
||||
function calculateZoomLevel(desiredImageWidthInKm, latitude) {
|
||||
const earthEquatorialCircumference = 40075016.686; // Earth's equatorial circumference in meters
|
||||
|
||||
// Calculate the distance covered by one degree of longitude at the given latitude
|
||||
const metersPerLongitudeDegree = earthEquatorialCircumference * Math.cos((Math.PI / 180) * latitude) / 360;
|
||||
|
||||
const metersPerPixel = metersPerLongitudeDegree * 360 / (tileSizeInPixels * Math.pow(2, 20));
|
||||
|
||||
// Calculate the number of pixels needed to achieve the desired width
|
||||
const numberOfPixelsHorizontally = (desiredImageWidthInKm * 1000) / metersPerPixel;
|
||||
|
||||
// Calculate the appropriate zoom level
|
||||
const zoomLevel = Math.log2(tileSizeInPixels * Math.pow(2, 20) / numberOfPixelsHorizontally);
|
||||
|
||||
return zoomLevel;
|
||||
}
|
||||
|
||||
// Function to extract URL parameters
|
||||
function getUrlParameter(name) {
|
||||
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||
const results = regex.exec(location.search);
|
||||
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
const defaultSouthWestLat = 51.000;
|
||||
const defaultSouthWestLon = -1.000;
|
||||
const defaultNorthEastLat = 52.000;
|
||||
const defaultNorthEastLon = 1.000;
|
||||
|
||||
const southWestLat = parseFloat(getUrlParameter('southWestLat')) || defaultSouthWestLat;
|
||||
const southWestLon = parseFloat(getUrlParameter('southWestLon')) || defaultSouthWestLon;
|
||||
const northEastLat = parseFloat(getUrlParameter('northEastLat')) || defaultNorthEastLat;
|
||||
const northEastLon = parseFloat(getUrlParameter('northEastLon')) || defaultNorthEastLon;
|
||||
|
||||
// Calculate the longitudinal distance in degrees
|
||||
const lonDistanceInDegrees = Math.abs(northEastLon - southWestLon);
|
||||
|
||||
// Calculate the desired image width in kilometers
|
||||
const earthEquatorialCircumference = 40075016.686; // Earth's equatorial circumference in meters
|
||||
const metersPerLongitudeDegree = earthEquatorialCircumference * Math.cos((Math.PI / 180) * southWestLat) / 360;
|
||||
const desiredImageWidthInMeters = lonDistanceInDegrees * metersPerLongitudeDegree;
|
||||
const desiredImageWidthInKm = desiredImageWidthInMeters / 1000;
|
||||
console.log('desiredImageWidthInKm:', desiredImageWidthInKm);
|
||||
|
||||
const centerLat = (southWestLat + northEastLat) / 2;
|
||||
const centerLon = (southWestLon + northEastLon) / 2;
|
||||
|
||||
// Provide the latitude for adjustment
|
||||
const latitude = centerLat;
|
||||
const zoomLevel = calculateZoomLevel(desiredImageWidthInKm, latitude);
|
||||
console.log('Recommended zoom level:', zoomLevel);
|
||||
|
||||
const map = L.map('map').setView([centerLat, centerLon], zoomLevel);
|
||||
|
||||
// Use the Mapbox Satellite tile layer
|
||||
//~ L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/{z}/{x}/{y}?access_token=YOUR_MAPBOX_ACCESS_TOKEN', {
|
||||
//~ attribution: '© Mapbox',
|
||||
//~ tileSize: 512,
|
||||
//~ zoomOffset: -1,
|
||||
//~ accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN'
|
||||
//~ }).addTo(map);
|
||||
|
||||
//~ // Use the OpenAerialMap (OAM) tile layer for satellite view
|
||||
//~ L.tileLayer('https://tiles.openaerialmap.org/5d6d1e370f437a00106e06ef/0/{z}/{x}/{y}.jpg', {
|
||||
//~ attribution: '© OpenAerialMap',
|
||||
//~ maxZoom: 18
|
||||
//~ }).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue