mirror of
https://github.com/FoggedLens/iD.git
synced 2026-04-29 07:06:04 +02:00
Merge pull request #2266 from bhousel/bhousel-scale
Add linear map scale to footer section
This commit is contained in:
+45
-21
@@ -2181,7 +2181,8 @@ img.wiki-image {
|
||||
/* About Section
|
||||
------------------------------------------------------- */
|
||||
|
||||
.about-block {
|
||||
#footer {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
right:0;
|
||||
bottom:0;
|
||||
@@ -2192,13 +2193,47 @@ img.wiki-image {
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
||||
.about-block:hover {
|
||||
#footer:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#about {
|
||||
#scale-block {
|
||||
display: table-cell;
|
||||
vertical-align: bottom;
|
||||
width: 250px;
|
||||
height: 30px;
|
||||
float: left;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
#info-block {
|
||||
float: right;
|
||||
clear: right;
|
||||
}
|
||||
|
||||
#scale {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#scale text {
|
||||
font: 12px sans-serif;
|
||||
stroke: none;
|
||||
fill: #ccc;
|
||||
text-anchor: start;
|
||||
}
|
||||
|
||||
#scale path {
|
||||
fill: none;
|
||||
stroke: #ccc;
|
||||
stroke-width: 1;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
#about-list {
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
clear: right;
|
||||
}
|
||||
|
||||
.source-switch a {
|
||||
@@ -2240,29 +2275,18 @@ img.wiki-image {
|
||||
content: ', ';
|
||||
}
|
||||
|
||||
/* API Status */
|
||||
|
||||
.api-status {
|
||||
float: left;
|
||||
float: right;
|
||||
clear: both;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.api-status.offline,
|
||||
.api-status.readonly {
|
||||
.api-status.readonly,
|
||||
.api-status.error {
|
||||
background: red;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
/* Account Information */
|
||||
|
||||
.account {
|
||||
float: left;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.account .logout {
|
||||
margin-left:10px;
|
||||
border-left: 1px solid white;
|
||||
padding-left: 10px;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
/* Modals
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<script src='js/id/ui/notice.js'></script>
|
||||
<script src='js/id/ui/flash.js'></script>
|
||||
<script src='js/id/ui/save.js'></script>
|
||||
<script src='js/id/ui/scale.js'></script>
|
||||
<script src='js/id/ui/splash.js'></script>
|
||||
<script src='js/id/ui/spinner.js'></script>
|
||||
<script src='js/id/ui/restore.js'></script>
|
||||
|
||||
+30
-3
@@ -21,11 +21,38 @@ iD.geo.euclideanDistance = function(a, b) {
|
||||
var x = a[0] - b[0], y = a[1] - b[1];
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
};
|
||||
|
||||
// using WGS84 polar radius (6356752.314245179 m)
|
||||
// const = 2 * PI * r / 360
|
||||
iD.geo.latToMeters = function(dLat) {
|
||||
return dLat * 110946.257617;
|
||||
};
|
||||
|
||||
// using WGS84 equatorial radius (6378137.0 m)
|
||||
// const = 2 * PI * r / 360
|
||||
iD.geo.lonToMeters = function(dLon, atLat) {
|
||||
return Math.abs(atLat) >= 90 ? 0 :
|
||||
dLon * 111319.490793 * Math.abs(Math.cos(atLat * (Math.PI/180)));
|
||||
};
|
||||
|
||||
// using WGS84 polar radius (6356752.314245179 m)
|
||||
// const = 2 * PI * r / 360
|
||||
iD.geo.metersToLat = function(m) {
|
||||
return m / 110946.257617;
|
||||
};
|
||||
|
||||
// using WGS84 equatorial radius (6378137.0 m)
|
||||
// const = 2 * PI * r / 360
|
||||
iD.geo.metersToLon = function(m, atLat) {
|
||||
return Math.abs(atLat) >= 90 ? 0 :
|
||||
m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180)));
|
||||
};
|
||||
|
||||
// Equirectangular approximation of spherical distances on Earth
|
||||
iD.geo.sphericalDistance = function(a, b) {
|
||||
var x = Math.cos(a[1]*Math.PI/180) * (a[0] - b[0]),
|
||||
y = a[1] - b[1];
|
||||
return 6.3710E6 * Math.sqrt((x * x) + (y * y)) * Math.PI/180;
|
||||
var x = iD.geo.lonToMeters(a[0] - b[0], (a[1] + b[1]) / 2),
|
||||
y = iD.geo.latToMeters(a[1] - b[1]);
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
};
|
||||
|
||||
iD.geo.edgeEqual = function(a, b) {
|
||||
|
||||
+2
-2
@@ -58,8 +58,8 @@ _.extend(iD.geo.Extent.prototype, {
|
||||
},
|
||||
|
||||
padByMeters: function(meters) {
|
||||
var dLat = meters / 111200,
|
||||
dLon = meters / 111200 / Math.abs(Math.cos(this.center()[1]));
|
||||
var dLat = iD.geo.metersToLat(meters),
|
||||
dLon = iD.geo.metersToLon(meters, this.center()[1]);
|
||||
return iD.geo.Extent(
|
||||
[this[0][0] - dLon, this[0][1] - dLat],
|
||||
[this[1][0] + dLon, this[1][1] + dLat]);
|
||||
|
||||
+17
-12
@@ -80,23 +80,24 @@ iD.ui = function(context) {
|
||||
.attr('class', 'map-control help-control')
|
||||
.call(iD.ui.Help(context));
|
||||
|
||||
var about = content.append('div')
|
||||
.attr('class','col12 about-block fillD');
|
||||
var footer = content.append('div')
|
||||
.attr('id', 'footer')
|
||||
.attr('class', 'fillD');
|
||||
|
||||
about.append('div')
|
||||
.attr('class', 'api-status')
|
||||
.call(iD.ui.Status(context));
|
||||
footer.append('div')
|
||||
.attr('id', 'scale-block')
|
||||
.call(iD.ui.Scale(context));
|
||||
|
||||
var linkList = footer.append('div')
|
||||
.attr('id', 'info-block')
|
||||
.append('ul')
|
||||
.attr('id', 'about-list')
|
||||
.attr('class', 'link-list');
|
||||
|
||||
if (!context.embed()) {
|
||||
about.append('div')
|
||||
.attr('class', 'account')
|
||||
.call(iD.ui.Account(context));
|
||||
linkList.call(iD.ui.Account(context));
|
||||
}
|
||||
|
||||
var linkList = about.append('ul')
|
||||
.attr('id', 'about')
|
||||
.attr('class', 'link-list');
|
||||
|
||||
linkList.append('li')
|
||||
.append('a')
|
||||
.attr('target', '_blank')
|
||||
@@ -123,6 +124,10 @@ iD.ui = function(context) {
|
||||
.attr('tabindex', -1)
|
||||
.call(iD.ui.Contributors(context));
|
||||
|
||||
footer.append('div')
|
||||
.attr('class', 'api-status')
|
||||
.call(iD.ui.Status(context));
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
return context.save();
|
||||
};
|
||||
|
||||
+20
-7
@@ -3,20 +3,25 @@ iD.ui.Account = function(context) {
|
||||
|
||||
function update(selection) {
|
||||
if (!connection.authenticated()) {
|
||||
selection.html('')
|
||||
selection.selectAll('#userLink, #logoutLink')
|
||||
.style('display', 'none');
|
||||
return;
|
||||
}
|
||||
|
||||
selection.style('display', 'block');
|
||||
|
||||
connection.userDetails(function(err, details) {
|
||||
selection.html('');
|
||||
var userLink = selection.select('#userLink'),
|
||||
logoutLink = selection.select('#logoutLink');
|
||||
|
||||
userLink.html('');
|
||||
logoutLink.html('');
|
||||
|
||||
if (err) return;
|
||||
|
||||
selection.selectAll('#userLink, #logoutLink')
|
||||
.style('display', 'list-item');
|
||||
|
||||
// Link
|
||||
var userLink = selection.append('a')
|
||||
userLink.append('a')
|
||||
.attr('href', connection.userURL(details.display_name))
|
||||
.attr('target', '_blank');
|
||||
|
||||
@@ -35,7 +40,7 @@ iD.ui.Account = function(context) {
|
||||
.attr('class', 'label')
|
||||
.text(details.display_name);
|
||||
|
||||
selection.append('a')
|
||||
logoutLink.append('a')
|
||||
.attr('class', 'logout')
|
||||
.attr('href', '#')
|
||||
.text(t('logout'))
|
||||
@@ -47,7 +52,15 @@ iD.ui.Account = function(context) {
|
||||
}
|
||||
|
||||
return function(selection) {
|
||||
connection.on('auth', function() { update(selection); });
|
||||
selection.append('li')
|
||||
.attr('id', 'logoutLink')
|
||||
.style('display', 'none');
|
||||
|
||||
selection.append('li')
|
||||
.attr('id', 'userLink')
|
||||
.style('display', 'none');
|
||||
|
||||
connection.on('auth.account', function() { update(selection); });
|
||||
update(selection);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
iD.ui.Scale = function(context) {
|
||||
var projection = context.projection,
|
||||
imperial = (iD.detect().locale === 'en-us'),
|
||||
maxLength = 180,
|
||||
tickHeight = 8;
|
||||
|
||||
function scaleDefs(loc1, loc2) {
|
||||
var lat = (loc2[1] + loc1[1]) / 2,
|
||||
conversion = (imperial ? 3.28084 : 1),
|
||||
dist = iD.geo.lonToMeters(loc2[0] - loc1[0], lat) * conversion,
|
||||
scale = { dist: 0, px: 0, text: '' },
|
||||
buckets, i, val, dLon;
|
||||
|
||||
if (imperial) {
|
||||
buckets = [5280000, 528000, 52800, 5280, 500, 50, 5, 1];
|
||||
} else {
|
||||
buckets = [5000000, 500000, 50000, 5000, 500, 50, 5, 1];
|
||||
}
|
||||
|
||||
// determine a user-friendly endpoint for the scale
|
||||
for (i = 0; i < buckets.length; i++) {
|
||||
val = buckets[i];
|
||||
if (dist >= val) {
|
||||
scale.dist = Math.floor(dist / val) * val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dLon = iD.geo.metersToLon(scale.dist / conversion, lat);
|
||||
scale.px = Math.round(projection([loc1[0] + dLon, loc1[1]])[0]);
|
||||
|
||||
if (imperial) {
|
||||
if (scale.dist >= 5280) {
|
||||
scale.dist /= 5280;
|
||||
scale.text = String(scale.dist) + ' mi';
|
||||
} else {
|
||||
scale.text = String(scale.dist) + ' ft';
|
||||
}
|
||||
} else {
|
||||
if (scale.dist >= 1000) {
|
||||
scale.dist /= 1000;
|
||||
scale.text = String(scale.dist) + ' km';
|
||||
} else {
|
||||
scale.text = String(scale.dist) + ' m';
|
||||
}
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
function update(selection) {
|
||||
// choose loc1, loc2 along bottom of viewport (near where the scale will be drawn)
|
||||
var dims = context.map().dimensions(),
|
||||
loc1 = projection.invert([0, dims[1]]),
|
||||
loc2 = projection.invert([maxLength, dims[1]]),
|
||||
scale = scaleDefs(loc1, loc2);
|
||||
|
||||
selection.select('#scalepath')
|
||||
.attr('d', 'M0.5,0.5v' + tickHeight + 'h' + scale.px + 'v-' + tickHeight);
|
||||
|
||||
selection.select('#scaletext')
|
||||
.attr('x', scale.px + 8)
|
||||
.attr('y', tickHeight)
|
||||
.text(scale.text);
|
||||
}
|
||||
|
||||
return function(selection) {
|
||||
var g = selection.append('svg')
|
||||
.attr('id', 'scale')
|
||||
.append('g')
|
||||
.attr('transform', 'translate(10,11)');
|
||||
|
||||
g.append('path').attr('id', 'scalepath');
|
||||
g.append('text').attr('id', 'scaletext');
|
||||
|
||||
update(selection);
|
||||
|
||||
context.map().on('move.scale', function() {
|
||||
update(selection);
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -85,6 +85,7 @@
|
||||
<script src='../js/id/ui/notice.js'></script>
|
||||
<script src='../js/id/ui/flash.js'></script>
|
||||
<script src='../js/id/ui/save.js'></script>
|
||||
<script src='../js/id/ui/scale.js'></script>
|
||||
<script src='../js/id/ui/splash.js'></script>
|
||||
<script src='../js/id/ui/restore.js'></script>
|
||||
<script src='../js/id/ui/tag_reference.js'></script>
|
||||
|
||||
+77
-5
@@ -57,6 +57,78 @@ describe('iD.geo', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.latToMeters', function() {
|
||||
it('0 degrees latitude is 0 meters', function() {
|
||||
expect(iD.geo.latToMeters(0)).to.eql(0);
|
||||
});
|
||||
it('1 degree latitude is approx 111 km', function() {
|
||||
expect(iD.geo.latToMeters(1)).to.be.within(110E3, 112E3);
|
||||
});
|
||||
it('-1 degree latitude is approx -111 km', function() {
|
||||
expect(iD.geo.latToMeters(-1)).to.be.within(-112E3, -110E3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.lonToMeters', function() {
|
||||
it('0 degrees longitude is 0 km', function() {
|
||||
expect(iD.geo.lonToMeters(0, 0)).to.eql(0);
|
||||
});
|
||||
it('distance of 1 degree longitude varies with latitude', function() {
|
||||
expect(iD.geo.lonToMeters(1, 0)).to.be.within(110E3, 112E3);
|
||||
expect(iD.geo.lonToMeters(1, 15)).to.be.within(107E3, 108E3);
|
||||
expect(iD.geo.lonToMeters(1, 30)).to.be.within(96E3, 97E3);
|
||||
expect(iD.geo.lonToMeters(1, 45)).to.be.within(78E3, 79E3);
|
||||
expect(iD.geo.lonToMeters(1, 60)).to.be.within(55E3, 56E3);
|
||||
expect(iD.geo.lonToMeters(1, 75)).to.be.within(28E3, 29E3);
|
||||
expect(iD.geo.lonToMeters(1, 90)).to.eql(0);
|
||||
});
|
||||
it('distance of -1 degree longitude varies with latitude', function() {
|
||||
expect(iD.geo.lonToMeters(-1, 0)).to.be.within(-112E3, -110E3);
|
||||
expect(iD.geo.lonToMeters(-1, -15)).to.be.within(-108E3, -107E3);
|
||||
expect(iD.geo.lonToMeters(-1, -30)).to.be.within(-97E3, -96E3);
|
||||
expect(iD.geo.lonToMeters(-1, -45)).to.be.within(-79E3, -78E3);
|
||||
expect(iD.geo.lonToMeters(-1, -60)).to.be.within(-56E3, -55E3);
|
||||
expect(iD.geo.lonToMeters(-1, -75)).to.be.within(-29E3, -28E3);
|
||||
expect(iD.geo.lonToMeters(-1, -90)).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.metersToLat', function() {
|
||||
it('0 meters is 0 degrees latitude', function() {
|
||||
expect(iD.geo.metersToLat(0)).to.eql(0);
|
||||
});
|
||||
it('111 km is approx 1 degree latitude', function() {
|
||||
expect(iD.geo.metersToLat(111E3)).to.be.within(0.995, 1.005);
|
||||
});
|
||||
it('-111 km is approx -1 degree latitude', function() {
|
||||
expect(iD.geo.metersToLat(-111E3)).to.be.within(-1.005, -0.995);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.metersToLon', function() {
|
||||
it('0 meters is 0 degrees longitude', function() {
|
||||
expect(iD.geo.metersToLon(0, 0)).to.eql(0);
|
||||
});
|
||||
it('distance of 1 degree longitude varies with latitude', function() {
|
||||
expect(iD.geo.metersToLon(111320, 0)).to.be.within(0.995, 1.005);
|
||||
expect(iD.geo.metersToLon(107551, 15)).to.be.within(0.995, 1.005);
|
||||
expect(iD.geo.metersToLon(96486, 30)).to.be.within(0.995, 1.005);
|
||||
expect(iD.geo.metersToLon(78847, 45)).to.be.within(0.995, 1.005);
|
||||
expect(iD.geo.metersToLon(55800, 60)).to.be.within(0.995, 1.005);
|
||||
expect(iD.geo.metersToLon(28902, 75)).to.be.within(0.995, 1.005);
|
||||
expect(iD.geo.metersToLon(1, 90)).to.eql(0);
|
||||
});
|
||||
it('distance of -1 degree longitude varies with latitude', function() {
|
||||
expect(iD.geo.metersToLon(-111320, 0)).to.be.within(-1.005, -0.995);
|
||||
expect(iD.geo.metersToLon(-107551, 15)).to.be.within(-1.005, -0.995);
|
||||
expect(iD.geo.metersToLon(-96486, 30)).to.be.within(-1.005, -0.995);
|
||||
expect(iD.geo.metersToLon(-78847, 45)).to.be.within(-1.005, -0.995);
|
||||
expect(iD.geo.metersToLon(-55800, 60)).to.be.within(-1.005, -0.995);
|
||||
expect(iD.geo.metersToLon(-28902, 75)).to.be.within(-1.005, -0.995);
|
||||
expect(iD.geo.metersToLon(-1, 90)).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.sphericalDistance', function() {
|
||||
it('distance between two same points is zero', function() {
|
||||
var a = [0, 0],
|
||||
@@ -66,22 +138,22 @@ describe('iD.geo', function() {
|
||||
it('a straight 1 degree line at the equator is aproximately 111 km', function() {
|
||||
var a = [0, 0],
|
||||
b = [1, 0];
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(100E3,120E3);
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(110E3, 112E3);
|
||||
});
|
||||
it('a pythagorean triangle is right', function() {
|
||||
it('a pythagorean triangle is (nearly) right', function() {
|
||||
var a = [0, 0],
|
||||
b = [4, 3];
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(500E3,600E3);
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(555E3, 556E3);
|
||||
});
|
||||
it('east-west distances at high latitude are shorter', function() {
|
||||
var a = [0, 60],
|
||||
b = [1, 60];
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(50E3,60E3);
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(55E3, 56E3);
|
||||
});
|
||||
it('north-south distances at high latitude are not shorter', function() {
|
||||
var a = [0, 60],
|
||||
b = [0, 61];
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(100E3,120E3);
|
||||
expect(iD.geo.sphericalDistance(a, b)).to.be.within(110E3, 112E3);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user