Расчет долготы и широты Меркатора до x и y на обрезанной карте (Великобритании)
У меня есть это изображение: https://imgur.com/99tSz.png. Карта Великобритании (не включая Южную Ирландию).
Мне успешно удалось получить широту и долготу и нанести их на эту карту, взяв крайнюю левую долготу и крайнюю правую долготу Великобритании и используя их, чтобы определить, где поставить точку на карте.
Это код (для использования в Processing.js, но может использоваться как js или как угодно):
// Size of the map
int width = 538;
int height = 811;
// X and Y boundaries
float westLong = -8.166667;
float eastLong = 1.762833;
float northLat = 58.666667;
float southLat = 49.95;
void drawPoint(float latitude, float longitude){
fill(#000000);
x = width * ((westLong-longitude)/(westLong-eastLong));
y = (height * ((northLat-latitude)/(northLat-southLat)));
console.log(x + ", " + y);
ellipseMode(RADIUS);
ellipse(x, y, 2, 2);
}
Однако я не смог реализовать проекцию Меркатора на эти значения. Графики достаточно точны, но они недостаточно хороши, и эта проекция решит эту проблему.
Я не могу понять, как это сделать. Все примеры, которые я нахожу, объясняют, как это сделать для всего мира. Это хороший источник примеров, объясняющих, как реализовать проекцию, но я не смог заставить ее работать.
Другой ресурс - Экстремальные точки Соединенного Королевства, где я получил значения широты и долготы ограничительной рамки вокруг Великобритании. Они также здесь:
northLat = 58.666667;
northLong = -3.366667;
eastLat = 52.481167;
eastLong = 1.762833;
southLat = 49.95;
southLong = -5.2;
westLat = 54.45;
westLong = -8.166667;
Если бы кто-нибудь мог помочь мне с этим, я был бы очень признателен!
Спасибо
10 ответов
Я думаю, что стоит иметь в виду, что не все плоские карты являются проекциями Меркатора. Не зная больше об этой карте, трудно быть уверенным. Вы можете обнаружить, что большинство карт небольшой области мира с большей вероятностью представляют собой проекцию конического типа, где область интереса на карте "более плоская", чем на глобальной проекции Меркатора. Это тем более важно, чем дальше вы уходите от экватора (а Великобритания достаточно далеко, чтобы это имело значение).
Возможно, вам удастся подойти "достаточно близко" с помощью вычислений, которые вы пытаетесь, но для лучшей точности вы можете либо использовать карту с четко определенной проекцией, либо создать свою собственную карту.
Я написал функцию, которая делает именно то, что вы искали. Я знаю, что уже немного поздно, но, возможно, есть другие люди, заинтересованные в этом.
Вам нужна карта, которая является проекцией Меркатора, и вам необходимо знать ее широту и долготу. Вы получаете отличные настраиваемые карты меркатора с идеально подходящими позициями широты и долготы от TileMill, которая является бесплатным программным обеспечением от MapBox!
Я использую этот скрипт и проверил его с некоторыми позициями Google Earth. Он отлично работал на уровне пикселей. На самом деле я не проверял это на разных или больших картах. Я надеюсь, что это поможет вам!
Рафаэль;)
<?php
$mapWidth = 1500;
$mapHeight = 1577;
$mapLonLeft = 9.8;
$mapLonRight = 10.2;
$mapLonDelta = $mapLonRight - $mapLonLeft;
$mapLatBottom = 53.45;
$mapLatBottomDegree = $mapLatBottom * M_PI / 180;
function convertGeoToPixel($lat, $lon)
{
global $mapWidth, $mapHeight, $mapLonLeft, $mapLonDelta, $mapLatBottom, $mapLatBottomDegree;
$x = ($lon - $mapLonLeft) * ($mapWidth / $mapLonDelta);
$lat = $lat * M_PI / 180;
$worldMapWidth = (($mapWidth / $mapLonDelta) * 360) / (2 * M_PI);
$mapOffsetY = ($worldMapWidth / 2 * log((1 + sin($mapLatBottomDegree)) / (1 - sin($mapLatBottomDegree))));
$y = $mapHeight - (($worldMapWidth / 2 * log((1 + sin($lat)) / (1 - sin($lat)))) - $mapOffsetY);
return array($x, $y);
}
$position = convertGeoToPixel(53.7, 9.95);
echo "x: ".$position[0]." / ".$position[1];
?>
Вот изображение, которое я создал с помощью TileMill и которое я использовал в этом примере:
В дополнение к тому, что написал Рафаэль Вихманн (спасибо, кстати!), Есть обратная функция в actionscript:
function convertPixelToGeo(tx:Number, ty:Number):Point
{
/* called worldMapWidth in Raphael's Code, but I think that's the radius since it's the map width or circumference divided by 2*PI */
var worldMapRadius:Number = mapWidth / mapLonDelta * 360/(2 * Math.PI);
var mapOffsetY:Number = ( worldMapRadius / 2 * Math.log( (1 + Math.sin(mapLatBottomRadian) ) / (1 - Math.sin(mapLatBottomRadian)) ));
var equatorY:Number = mapHeight + mapOffsetY;
var a:Number = (equatorY-ty)/worldMapRadius;
var lat:Number = 180/Math.PI * (2 * Math.atan(Math.exp(a)) - Math.PI/2);
var long:Number = mapLonLeft+tx/mapWidth*mapLonDelta;
return new Point(lat,long);
}
Вот еще одна реализация Javascript. Это упрощение решения @Rob Willet, описанного выше. Вместо того, чтобы требовать вычисленные значения в качестве параметров для функции, он только требует основных значений и вычисляет все из них:
function convertGeoToPixel(latitude, longitude,
mapWidth, // in pixels
mapHeight, // in pixels
mapLngLeft, // in degrees. the longitude of the left side of the map (i.e. the longitude of whatever is depicted on the left-most part of the map image)
mapLngRight, // in degrees. the longitude of the right side of the map
mapLatBottom) // in degrees. the latitude of the bottom of the map
{
const mapLatBottomRad = mapLatBottom * Math.PI / 180
const latitudeRad = latitude * Math.PI / 180
const mapLngDelta = (mapLngRight - mapLngLeft)
const worldMapWidth = ((mapWidth / mapLngDelta) * 360) / (2 * Math.PI)
const mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomRad)) / (1 - Math.sin(mapLatBottomRad))))
const x = (longitude - mapLngLeft) * (mapWidth / mapLngDelta)
const y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(latitudeRad)) / (1 - Math.sin(latitudeRad)))) - mapOffsetY)
return {x, y} // the pixel x,y value of this point on the map image
}
Я преобразовал код PHP, предоставленный Raphael, в JavaScript и могу подтвердить, что он работает, и этот код работает сам. Вся заслуга Рафаэля.
/*
var mapWidth = 1500;
var mapHeight = 1577;
var mapLonLeft = 9.8;
var mapLonRight = 10.2;
var mapLonDelta = mapLonRight - mapLonLeft;
var mapLatBottom = 53.45;
var mapLatBottomDegree = mapLatBottom * Math.PI / 180;
*/
function convertGeoToPixel(latitude, longitude ,
mapWidth , // in pixels
mapHeight , // in pixels
mapLonLeft , // in degrees
mapLonDelta , // in degrees (mapLonRight - mapLonLeft);
mapLatBottom , // in degrees
mapLatBottomDegree) // in Radians
{
var x = (longitude - mapLonLeft) * (mapWidth / mapLonDelta);
latitude = latitude * Math.PI / 180;
var worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
var mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));
var y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(latitude)) / (1 - Math.sin(latitude)))) - mapOffsetY);
return { "x": x , "y": y};
}
Я знаю, что вопрос задавался некоторое время назад, но библиотека Proj4JS идеально подходит для преобразования различных проекций карт в JavaScript.
Карты Великобритании, как правило, используют национальную сетку OSGB, основанную на поперечной проекции Меркатора. То есть. как обычный Меркатор, но повернутый на 90 градусов, так что "экватор" становится меридианом.
Фрагмент @Xarinko Actionscript в Javascript (с некоторыми значениями тестирования)
var mapWidth = 1500;
var mapHeight = 1577;
var mapLonLeft = 9.8;
var mapLonRight = 10.2;
var mapLonDelta = mapLonRight - mapLonLeft;
var mapLatBottom = 53.45;
var mapLatBottomRadian = mapLatBottom * Math.PI / 180;
function convertPixelToGeo(tx, ty)
{
/* called worldMapWidth in Raphael's Code, but I think that's the radius since it's the map width or circumference divided by 2*PI */
var worldMapRadius = mapWidth / mapLonDelta * 360/(2 * Math.PI);
var mapOffsetY = ( worldMapRadius / 2 * Math.log( (1 + Math.sin(mapLatBottomRadian) ) / (1 - Math.sin(mapLatBottomRadian)) ));
var equatorY = mapHeight + mapOffsetY;
var a = (equatorY-ty)/worldMapRadius;
var lat = 180/Math.PI * (2 * Math.atan(Math.exp(a)) - Math.PI/2);
var long = mapLonLeft+tx/mapWidth*mapLonDelta;
return [lat,long];
}
convertPixelToGeo(241,444)
Реализация C#:
private Point ConvertGeoToPixel(
double latitude, double longitude, // The coordinate to translate
int imageWidth, int imageHeight, // The dimensions of the target space (in pixels)
double mapLonLeft, double mapLonRight, double mapLatBottom // The bounds of the target space (in geo coordinates)
) {
double mapLatBottomRad = mapLatBottom * Math.PI / 180;
double latitudeRad = latitude * Math.PI / 180;
double mapLonDelta = mapLonRight - mapLonLeft;
double worldMapWidth = (imageWidth / mapLonDelta * 360) / (2 * Math.PI);
double mapOffsetY = worldMapWidth / 2 * Math.Log((1 + Math.Sin(mapLatBottomRad)) / (1 - Math.Sin(mapLatBottomRad)));
double x = (longitude - mapLonLeft) * (imageWidth / mapLonDelta);
double y = imageHeight - ((worldMapWidth / 2 * Math.Log((1 + Math.Sin(latitudeRad)) / (1 - Math.Sin(latitudeRad)))) - mapOffsetY);
return new Point()
{
X = Convert.ToInt32(x),
Y = Convert.ToInt32(y)
};
}
Эта функция отлично работает для меня, потому что я хочу определить mapHeight на основе карты, которую я хочу построить. Я создаю PDF-карты. Все, что мне нужно сделать, это передать максимальный широту, минимальный долг на карте, и он возвращает размер пикселей для карты как [высота, ширина].
convertGeoToPixel (maxlatitude, maxlongitude)
Одна заметка на последнем шаге, где установлен $y, не вычитайте вычисление из mapHeight, если ваша система координат 'xy' начинается внизу / слева, как в случае с PDF, это инвертирует карту.
$y = (($worldMapWidth / 2 * log((1 + sin($lat)) / (1 - sin($lat)))) - $mapOffsetY);
Если вы хотите избежать некоторых запутанных аспектов проекций широты и ширины, присущих Proj4JS, вы можете использовать D3, который предлагает множество встроенных проекций и прекрасно отображает. Вот интерактивный пример нескольких разновидностей азимутальных проекций. Я предпочитаю Альберса для карт США.
Если D3 не является опцией конечного пользователя - скажем, вам нужно поддерживать IE 7/8 - вы можете выполнить рендеринг в D3 и затем получить координаты xy из результирующего файла SVG, который генерирует D3. Затем вы можете отобразить эти координаты XY в Рафаэле.