【问题标题】:Generate random geo coordinates within specific radius from seed point从种子点生成特定半径内的随机地理坐标
【发布时间】:2015-09-20 10:45:08
【问题描述】:

我正在使用以下函数从种子点生成指定半径内的随机地理坐标:

function randomGeo(center, radius) {
    var y0 = center.latitude;
    var x0 = center.longitude;
    var rd = radius / 111300;

    var u = Math.random();
    var v = Math.random();

    var w = rd * Math.sqrt(u);
    var t = 2 * Math.PI * v;
    var x = w * Math.cos(t);
    var y = w * Math.sin(t);

    var xp = x / Math.cos(y0);

    return {
        'latitude': y + y0,
        'longitude': xp + x0
    };
}

我使用 2000m 半径和以下种子点循环执行多次:

location: { // Oxford
    latitude: 51.73213,
    longitude: -1.20631
}

我希望所有这些结果都在 2000 米以内;相反,我看到的值超过 10000m:

[ { latitude: 51.73256540025445, longitude: -1.3358092771716716 },   // 3838.75070783092
  { latitude: 51.7214165686511, longitude: -1.1644147572878725 },    // 3652.1890457730474
  { latitude: 51.71721400063117, longitude: -1.2082082568884593 },   // 8196.861603477768
  { latitude: 51.73583824510363, longitude: -1.0940424351649711 },   // 5104.820455873758
  { latitude: 51.74017571473442, longitude: -1.3150742602532257 },   // 4112.3279147866215
  { latitude: 51.73496163915278, longitude: -1.0379454413532996 },   // 9920.01459343298
  { latitude: 51.73582333121239, longitude: -1.0939302282840453 },   // 11652.160906253064
  { latitude: 51.72145745285658, longitude: -1.2491630482776055 },   // 7599.550622138115
  { latitude: 51.73036335927129, longitude: -1.3516902043395063 },   // 8348.276271205428
  { latitude: 51.748104753808924, longitude: -1.2669212014250266 },  // 8880.760669882042
  { latitude: 51.72010719621805, longitude: -1.327161328951446 },    // 8182.466715589904
  { latitude: 51.725727610071125, longitude: -1.0691503599266818 } ] // 2026.3687763449955

鉴于我(无耻!)从其他地方剽窃了这个解决方案(尽管我见过几个类似的实现),我似乎无法弄清楚数学哪里出了问题。

(另外,如果你想要的话,这就是我计算距离的方式。很确定这是正确的。)

function distance(lat1, lon1, lat2, lon2) {
    var R = 6371000;
    var a = 0.5 - Math.cos((lat2 - lat1) * Math.PI / 180) / 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * (1 - Math.cos((lon2 - lon1) * Math.PI / 180)) / 2;
    return R * 2 * Math.asin(Math.sqrt(a));
}

【问题讨论】:

标签: javascript geolocation


【解决方案1】:

问题似乎源于这样一个事实,即这只是一个不准确的计算,具体取决于您使用的中心点。特别是这一行:

var xp = x / Math.cos(y0);

删除这条线并将经度更改为

'longitude': x + x0

似乎将所有点保持在指定半径内,尽管没有这条线,在某些情况下,这些点似乎不会完全从东到西填充。

无论如何,我发现有人遇到类似问题here 与其他人的 Matlab 代码作为可能的解决方案。如果您想使用不同的公式,则取决于您需要随机点的均匀分布程度。

这是您提供的公式的谷歌地图可视化:

<!doctype html>
<html>
<head>
    <script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>

    <script>
    var distanceLimit = 2000; //in meters
    var numberRandomPoints = 200;
    var mapZoomLevel = 11;
    var locationindex = 0;
    var locations = [
        {'name': 'Oxford, England', 'latitude': 51.73213, 'longitude': -1.20631},
        {'name': 'Quito, Ecuador', 'latitude': -0.2333, 'longitude': -78.5167},
        {'name': 'Ushuaia, Argentina', 'latitude': -54.8000, 'longitude': -68.3000},
        {'name': 'McMurdo Station, Antartica', 'latitude': -77.847281, 'longitude': 166.667942},
        {'name': 'Norilsk, Siberia', 'latitude': 69.3333, 'longitude': 88.2167},
        {'name': 'Greenwich, England', 'latitude': 51.4800, 'longitude': 0.0000},
        {'name': 'Suva, Fiji', 'latitude': -18.1416, 'longitude': 178.4419},
        {'name': 'Tokyo, Japan', 'latitude': 35.6833, 'longitude': 139.6833},
        {'name': 'Mumbai, India', 'latitude': 18.9750, 'longitude': 72.8258},
        {'name': 'New York, USA', 'latitude': 40.7127, 'longitude': -74.0059},
        {'name': 'Moscow, Russia', 'latitude': 55.7500, 'longitude': 37.6167},
        {'name': 'Cape Town, South Africa', 'latitude': -33.9253, 'longitude': 18.4239},
        {'name': 'Cairo, Egypt', 'latitude': 30.0500, 'longitude': 31.2333},
        {'name': 'Sydney, Australia', 'latitude': -33.8650, 'longitude': 151.2094},
    ];
    </script>
</head>
<body>
<div id="topbar">
    <select id="location_switch">
    <script>
        for (i=0; i<locations.length; i++) {
            document.write('<option value="' + i + '">' + locations[i].name + '</option>');
        }
    </script>
    </select>
    <img src="http://google.com/mapfiles/ms/micons/ylw-pushpin.png" style="height:15px;"> = Center
    <img src="https://maps.gstatic.com/mapfiles/ms2/micons/red.png" style="height:15px;"> = No Longitude Adjustment
    <img src="https://maps.gstatic.com/mapfiles/ms2/micons/pink.png" style="height:15px;"> = With Longitude Adjustment (var xp = x / Math.cos(y0);)
</div>

<div id="map_canvas" style="position:absolute; top:30px; left:0px; height:100%; height:calc(100% - 30px); width:100%;overflow:hidden;"></div>

<script>
var markers = [];
var currentcircle;

//Create the default map
var mapcenter = new google.maps.LatLng(locations[locationindex].latitude, locations[locationindex].longitude);
var myOptions = {
    zoom: mapZoomLevel,
    scaleControl: true,
    center: mapcenter
};
var map = new google.maps.Map(document.getElementById('map_canvas'), myOptions);

//Draw default items
var centermarker = addCenterMarker(mapcenter, locations[locationindex].name + '<br>' + locations[locationindex].latitude + ', ' + locations[locationindex].longitude);
var mappoints = generateMapPoints(locations[locationindex], distanceLimit, numberRandomPoints);
drawRadiusCircle(map, centermarker, distanceLimit);
createRandomMapMarkers(map, mappoints);

//Create random lat/long coordinates in a specified radius around a center point
function randomGeo(center, radius) {
    var y0 = center.latitude;
    var x0 = center.longitude;
    var rd = radius / 111300; //about 111300 meters in one degree

    var u = Math.random();
    var v = Math.random();

    var w = rd * Math.sqrt(u);
    var t = 2 * Math.PI * v;
    var x = w * Math.cos(t);
    var y = w * Math.sin(t);

    //Adjust the x-coordinate for the shrinking of the east-west distances
    var xp = x / Math.cos(y0);

    var newlat = y + y0;
    var newlon = x + x0;
    var newlon2 = xp + x0;

    return {
        'latitude': newlat.toFixed(5),
        'longitude': newlon.toFixed(5),
        'longitude2': newlon2.toFixed(5),
        'distance': distance(center.latitude, center.longitude, newlat, newlon).toFixed(2),
        'distance2': distance(center.latitude, center.longitude, newlat, newlon2).toFixed(2),
    };
}

//Calc the distance between 2 coordinates as the crow flies
function distance(lat1, lon1, lat2, lon2) {
    var R = 6371000;
    var a = 0.5 - Math.cos((lat2 - lat1) * Math.PI / 180) / 2 + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * (1 - Math.cos((lon2 - lon1) * Math.PI / 180)) / 2;
    return R * 2 * Math.asin(Math.sqrt(a));
}

//Generate a number of mappoints
function generateMapPoints(centerpoint, distance, amount) {
    var mappoints = [];
    for (var i=0; i<amount; i++) {
        mappoints.push(randomGeo(centerpoint, distance));
    }
    return mappoints;
}

//Add a unique center marker
function addCenterMarker(centerposition, title) {
    
    var infowindow = new google.maps.InfoWindow({
        content: title
    });
    
    var newmarker = new google.maps.Marker({
        icon: 'http://google.com/mapfiles/ms/micons/ylw-pushpin.png',
        position: mapcenter,
        map: map,
        title: title,
        zIndex: 3
    });
    
    google.maps.event.addListenerOnce(map, 'tilesloaded', function() {
        infowindow.open(map,newmarker);
    });
    
    markers.push(newmarker);
    return newmarker;
}

//Draw a circle on the map
function drawRadiusCircle (map, marker, distance) {
    currentcircle = new google.maps.Circle({
        map: map,
        radius: distance
    });
    currentcircle.bindTo('center', marker, 'position');
}

//Create markers for the randomly generated points
function createRandomMapMarkers(map, mappoints) {
    for (var i = 0; i < mappoints.length; i++) {
        //Map points without the east/west adjustment
        var newmappoint = new google.maps.LatLng(mappoints[i].latitude, mappoints[i].longitude);
        var marker = new google.maps.Marker({
            position:newmappoint,
            map: map,
            title: mappoints[i].latitude + ', ' + mappoints[i].longitude + ' | ' + mappoints[i].distance + 'm',
            zIndex: 2
        });
        markers.push(marker);

        //Map points with the east/west adjustment
        var newmappoint = new google.maps.LatLng(mappoints[i].latitude, mappoints[i].longitude2);
        var marker = new google.maps.Marker({
            icon: 'https://maps.gstatic.com/mapfiles/ms2/micons/pink.png',
            position:newmappoint,
            map: map,
            title: mappoints[i].latitude + ', ' + mappoints[i].longitude2 + ' | ' + mappoints[i].distance2 + 'm',
            zIndex: 1
        });
        markers.push(marker);
    }
}

//Destroy all markers
function clearMarkers() {
    for (var i = 0; i < markers.length; i++) {
        markers[i].setMap(null);
    }
    markers = [];
}

$('#location_switch').change(function() {
    var newlocation = $(this).val();
    
    clearMarkers();

    mapcenter = new google.maps.LatLng(locations[newlocation].latitude, locations[newlocation].longitude);
    map.panTo(mapcenter);
    centermarker = addCenterMarker(mapcenter, locations[newlocation].name + '<br>' + locations[newlocation].latitude + ', ' + locations[newlocation].longitude);
    mappoints = generateMapPoints(locations[newlocation], distanceLimit, numberRandomPoints);

    //Draw default items
    currentcircle.setMap(null);
    drawRadiusCircle(map, centermarker, distanceLimit);
    createRandomMapMarkers(map, mappoints);
});
</script>
</body>
</html>

【讨论】:

  • 到目前为止一切顺利,谢谢!好奇为什么它仍然没有从东到西填写?但它现在的行为似乎确实正确。
  • 我的理解是,如果地球是一个完美的球体,你的公式会起作用 :) 我删除的那一点是为了补偿地球不是一个完美的球体,但它不起作用全世界都一样。
  • 啊,你可能是对的。想知道我们是否可以弥补这一点?反正我想做的事情可能太复杂了哈哈
  • 我认为它变得更加复杂。 Haversine Formula 似乎是这些计算的基础。 This article on the earth's radius at different latitudes 提供了关于如何获得地球半径距离数的线索(它是赤道和两极半径的平均值)。据此,我猜我们需要第三个值以及坐标(该纬度处的地球半径),或者可以调整公式以在某个纬度上正常工作。
  • 确实如此。好的,我会接受你的回答,因为它解决了我眼前的问题。如果其他人出现并想贡献一个支持第三个参数的方法,超级!再次感谢;)
【解决方案2】:

您可以通过使用 vincenty 距离移动一些距离来生成具有随机方位角和距中心距离的点(请参阅此 stackoverflow answer)。例如,在 Python 中,您可以使用 geopy 包。

import random
from geopy import Point
from geopy.distance import geodesic


def generate_point(center: Point, radius: int) -> Point:
    radius_in_kilometers = radius * 1e-3
    random_distance = random.random() * radius_in_kilometers
    random_bearing = random.random() * 360
    return geodesic(kilometers=random_distance).destination(center, random_bearing)


radius = 2000
center = Point(51.73213, -1.20631)
points = [generate_point(center, radius) for _ in range(3000)]

距离通过以下方式确认:

assert all(geodesic(center, point).meters <= radius for point in points)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-28
    • 2020-12-09
    • 1970-01-01
    • 1970-01-01
    • 2019-07-02
    • 2023-01-07
    • 1970-01-01
    相关资源
    最近更新 更多