Расчет долготы и широты Меркатора для x и y на обрезанной карте (Великобритании)

У меня есть этот образ: http://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);    

}

Однако я не смог реализовать проекцию Меркатора на эти значения. Участки достаточно точны, но они недостаточно хороши, и эта проекция решила бы ее.

Я не могу понять, как это сделать. Все примеры, которые я нахожу, объясняют, как это сделать для всего мира. Это является хорошим ресурсом примеров, объясняющих, как реализовать проект, но я не смог заставить его работать.

Другим ресурсом является Extreme points of the United Kingdom, где я получил значения широты и долготы ограничительной рамки по всей Великобритании. Они также здесь:

northLat = 58.666667; 
northLong = -3.366667; 
eastLat = 52.481167; 
eastLong = 1.762833; 
southLat = 49.95;
southLong = -5.2; 
westLat = 54.45;
westLong = -8.166667;

Если бы кто-нибудь мог помочь мне в этом, я был бы очень признателен!

Спасибо

Ответ 1

Я думаю, стоит иметь в виду, что не все плоские карты являются проекциями Меркатора. Не зная больше о той карте, в частности, трудно быть уверенным. Вы можете обнаружить, что большинство карт небольшой области мира, скорее всего, будут проекцией конического типа, где область интересов на карте "плоская", чем на глобальной проекции Меркатора. Это особенно важно, чем дальше вы уходите от экватора (и Великобритания достаточно далеко, чтобы это имело значение).

Вы можете получить "достаточно близко" с помощью вычислений, которые вы пытаетесь, но для лучшей точности вы можете либо использовать карту с четко определенной проекцией, либо создать свою собственную карту.

Ответ 2

Я написал функцию, которая делает именно то, что вы искали. Я знаю это немного позже, но, возможно, есть и другие заинтересованные люди.

Вам нужна карта, которая является проекцией меркатора, и вам нужно знать положения латинского /lon вашей карты. Вы получаете отличные настроенные карты мерцаров с идеальными совпадающими положениями lat/lon от TileMill, который является свободным программным обеспечением от MapBox!

Я использую этот script и тестировал его с некоторыми позициями земли google. Он работал идеально на уровне пикселей. На самом деле я не тестировал это на разных или больших картах. Надеюсь, это поможет вам!

Рафаэль;)

<?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 и которое я использовал в этом примере: map image

Ответ 3

В дополнение к тому, что опубликовал Рафаэль Уичманн (спасибо, кстати!), вот обратная функция, в actionscript:

function convertPixelToGeo(tx:Number, ty:Number):Point
{   
    /* called worldMapWidth in Raphael Code, but I think that the radius since it 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);
}

Ответ 4

Я преобразовал PHP-код, предоставленный Рафаэлем, в 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};
}

Ответ 5

Я знаю, что вопрос был задан некоторое время назад, но библиотека Proj4JS идеально подходит для преобразования между различными проекциями карт в JavaScript.

Карты Великобритании, как правило, используют Национальную сетку OSGB, которая основана на проекции поперечного Меркатора. То есть. как обычный Меркатор, но повернутый на 90 градусов, так что "экватор" становится меридианом.

Ответ 6

Здесь другая реализация 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
}

Ответ 7

@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 Code, but I think that the radius since it 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)

Ответ 8

Если вы хотите избежать некоторых из более грязных аспектов проекций lat/lng, присущих Proj4JS, вы можете использовать D3, который предлагает много выпеченных проекций и красиво отображает. Здесь интерактивный пример нескольких вариантов азимутальных проекций. Я предпочитаю карты Альберса для США.

Если D3 не является конечным пользователем - скажем, вам нужно поддерживать IE 7/8 - вы можете визуализировать в D3, а затем зацепить координаты xy из результирующего SVG файла, который генерирует D3. Затем вы можете отобразить эти координаты xy в Рафаэле.

Ответ 9

Эта функция отлично работает для меня, потому что я хочу определить mapHeight на основе карты, которую я хочу построить. Я создаю карты PDF. Все, что мне нужно сделать, это передать карту max Lat, min Lon и вернуть размер пикселей для карты как [высота, ширина].

convertGeoToPixel (maxlatitude, maxlongitude)

Одна нота на последнем шаге, где задан параметр $y, не вычтите расчет из mapHeight, если ваша координатная система xy начинается в начале/слева, как в случае с PDF файлами, это инвертирует карту.

$y =  (($worldMapWidth / 2 * log((1 + sin($lat)) / (1 - sin($lat)))) - $mapOffsetY);

Ответ 10

Реализация С#:

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)
    };
}