随机地理坐标(在陆地上,避免海洋)

38

有没有聪明的想法来生成地球上地点的随机坐标(纬度/经度)?纬度/经度,精度为5位,并避开水域。

    double minLat = -90.00;
    double maxLat = 90.00;      
    double latitude = minLat + (double)(Math.random() * ((maxLat - minLat) + 1));
    double minLon = 0.00;
    double maxLon = 180.00;     
    double longitude = minLon + (double)(Math.random() * ((maxLon - minLon) + 1));
    DecimalFormat df = new DecimalFormat("#.#####");        
    log.info("latitude:longitude --> " + df.format(latitude) + "," + df.format(longitude));

也许我活在一个梦幻世界里,水这个话题是不可避免的...但希望有一种更美好、更清洁和更有效的方法来解决这个问题?

编辑

有些很棒的答案/想法——但是,如果我需要生成25000个坐标,去外部服务提供商可能不是最佳选择,因为延迟、成本和其他几个因素。


1
看看这个问题:https://dev59.com/kEfSa4cB1Zd3GeqPBv3F 一些答案非常好。 - Mister Smith
可能会尝试地理哈希 :) http://xkcd.com/426/ - jb.
这段代码不会在极地附近产生相当奇怪的分布吗? - aioobe
16个回答

19

解决水域问题基本上是一个数据问题,例如你是只想忽略海洋还是也需要忽略小溪流。要么你需要使用具备所需数据质量的服务,要么你需要自己获取数据并在本地运行。从您的编辑内容来看,似乎您想采用本地数据路线,因此我会专注于介绍一种方法。

一种方法是获取土地区域或水域的形状文件。然后,您可以生成一个随机点,并确定它是否与土地区域相交(或者反之,不与水域相交)。

要开始操作,您可以在这里获取一些低分辨率的数据(链接) ,以及在需要获得更好的海岸线或湖泊/河流等答案时,在这里获取更高分辨率的数据(链接)。 您提到您希望将点的精度保留到5个小数位,这相当于略大于1米。请注意,如果您获取匹配该精度的数据,则会有一个庞大的数据集。而且,如果您想要真正好的数据,需要为其支付费用。

一旦您获得了形状数据,您需要一些工具来帮助您确定随机点的交集。 Geotools 是一个很好的起点,可能适合您的需求。您还会看到 opengis 代码(文档在 geotools 网站下 - 不确定它们是否已被消耗或其他)和JTS以进行几何处理。使用这些工具,您可以快速打开形状文件并开始进行一些交叉查询。

    File f = new File ( "world.shp" );
    ShapefileDataStore dataStore = new ShapefileDataStore ( f.toURI ().toURL () );
    FeatureSource<SimpleFeatureType, SimpleFeature> featureSource = 
        dataStore.getFeatureSource ();
    String geomAttrName = featureSource.getSchema ()
        .getGeometryDescriptor ().getLocalName ();

    ResourceInfo resourceInfo = featureSource.getInfo ();
    CoordinateReferenceSystem crs = resourceInfo.getCRS ();
    Hints hints = GeoTools.getDefaultHints ();
    hints.put ( Hints.JTS_SRID, 4326 );
    hints.put ( Hints.CRS, crs );

    FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2 ( hints );
    GeometryFactory gf = JTSFactoryFinder.getGeometryFactory ( hints );

    Coordinate land = new Coordinate ( -122.0087, 47.54650 );
    Point pointLand = gf.createPoint ( land );
    Coordinate water = new Coordinate ( 0, 0 );
    Point pointWater = gf.createPoint ( water );

    Intersects filter = ff.intersects ( ff.property ( geomAttrName ), 
        ff.literal ( pointLand ) );
    FeatureCollection<SimpleFeatureType, SimpleFeature> features = featureSource
            .getFeatures ( filter );

    filter = ff.intersects ( ff.property ( geomAttrName ), 
        ff.literal ( pointWater ) );
    features = featureSource.getFeatures ( filter );

简要说明:

  1. 假设你得到的shapefile是多边形数据。对于线或点的交集,无法给出你想要的结果。
  2. 第一部分打开了shapefile-没什么有趣的内容
  3. 你需要获取给定文件的几何属性名称
  4. 坐标系统相关的内容 - 你在帖子中指定了经纬度,但GIS可能更加复杂。通常情况下,我指向您的数据是地理的,使用WGS84坐标系,这也是我在这里设置的。然而,如果你的情况不是这样的,那么你必须确保你在正确的坐标系下处理你的数据。如果这些听起来像胡言乱语,请上网搜索GIS /坐标系统/基准面/椭球体教程。
  5. 生成坐标几何和过滤器相当简单。得到的要素集将是空的,如果你的数据是陆地覆盖,这意味着该坐标位于水中;否则就不为空,意味着相反的情况。

注意:如果你用一个真正随机的点集来做这个操作,你会经常遇到水域,并且可能需要很长时间才能得到25k个点。你可以尝试更好地限定点的生成范围,比如去除大片的大西洋/太平洋/印度洋。

此外,您可能会发现交集查询速度过慢。如果是这样,你可能想考虑使用像GDAL这样的工具创建四叉树索引(qix)。我不记得geotools支持哪些索引类型了,不过。


我可以在这里提及我的Python解决方案吗?因为这个问题/答案真的很启发人,但对于像我这样的非Java开发人员来说相当令人沮丧。 - SylvainB

7
这个问题很久以前就被问过了,现在我有类似的需求。我正在考虑两种可能性: 1. 为随机生成器定义表面范围。 在这里,重要的是确定您所追求的精度水平。最简单的方法是采用非常宽松和近似的方法。在这种情况下,您可以将世界地图分成“方框”:

enter image description here

每个盒子都有自己的纬度和经度范围。首先,你需要随机选择一个盒子,然后在该盒子的边界内随机选择一个纬度和经度。
这里精度当然不是最好的...虽然这取决于 :) 如果你做好功课并定义了许多覆盖大多数复杂表面形状的盒子,则可能会对精度感到满意。
2. 列表项
一些API可以从坐标、地址、国家或地区返回大陆名称,这是WATER没有的功能。Google Maps API可以帮助解决这个问题。我没有深入研究过这个问题,但我认为这是可能的,尽管你必须对每个生成的坐标对运行检查,并在出现错误时重新运行。所以如果随机生成器一直将你扔进海洋中,你可能会遇到一些困难。
此外,一些水域确实属于国家、地区...所以精度不是很高。
对于我的需求,我选择“盒子”,因为我还想控制随机坐标的确切区域,而不介意它落在湖泊或河流上,只要不是开放海洋 :)

4
为了在经纬度上获得良好的均匀分布,您应该像这样做以获得正确的角度:
double longitude = Math.random() * Math.PI * 2;
double latitude = Math.acos(Math.random() * 2 - 1);

关于避免水体,你有现有的水域数据吗?如果没有,只需重新采样直到找到为止!如果你没有这些数据,那么似乎其他人对此有更好的建议...

希望这可以帮到你,祝好。


正确的方法来实现球面上均匀随机分布点的算法可以在http://mathworld.wolfram.com/SpherePointPicking.html找到。 - gb96
请注意,您的坐标是以弧度为单位的,并且您的经度范围在[0,2 * PI)之间,这是不正确的。它需要在[-PI,PI)弧度或[-180,180)度之间。 - gb96

4
  1. 下载一大堆仅包含陆地位置的KML文件。
  2. 从中提取出所有坐标 这里可能会有帮助
  3. 随机挑选它们。

3

有另一种方法可以使用Google Earth Api来解决这个问题。我知道它是javascript,但我认为这是一个新颖的解决方法。

无论如何,我在这里提供了一个完整的工作解决方案 - 请注意,它也适用于河流: http://www.msa.mmu.ac.uk/~fraser/ge/coord/

我使用的基本思路是在Google Earth Api中实现GEView对象hiTest方法

看一下来自Google的hitest的以下示例。 http://earth-api-samples.googlecode.com/svn/trunk/examples/hittest.html

hitTest方法会提供一个屏幕上的随机点(以像素坐标表示),它会返回一个GEHitTestResult对象,其中包含与该点对应的地理位置信息。如果使用GEPlugin.HIT_TEST_TERRAIN模式调用该方法,则可以将结果限制为仅限于陆地(地形),只要我们筛选高度> 1m的点即可。
这是我使用的实现hitTest的函数:
var hitTestTerrain = function()
{
    var x = getRandomInt(0, 200); // same pixel size as the map3d div height
    var y = getRandomInt(0, 200); // ditto for width
    var result = ge.getView().hitTest(x, ge.UNITS_PIXELS, y, ge.UNITS_PIXELS, ge.HIT_TEST_TERRAIN);
    var success = result && (result.getAltitude() > 1);
    return { success: success, result: result };
};

显然,您还希望从全球任何地方获得随机结果(不仅仅是从单个视点可见的随机点)。为了做到这一点,在每次成功的hitTestTerrain调用后,我移动地球视图。这是通过使用一个小的辅助函数实现的。
var flyTo = function(lat, lng, rng)
{
    lookAt.setLatitude(lat);
    lookAt.setLongitude(lng);
    lookAt.setRange(rng);
    ge.getView().setAbstractView(lookAt);
};

最后,这里是一个简化的主要代码块,调用这两个方法。
var getRandomLandCoordinates = function()
{
    var test = hitTestTerrain();
    if (test.success)
    {
        coords[coords.length] = { lat: test.result.getLatitude(), lng: test.result.getLongitude() };
    }

    if (coords.length <= number)
    {
       getRandomLandCoordinates();
    }
    else
    {
       displayResults();
    }
};

所以,地球会随机移动到一个位置。

其他函数只是帮助生成随机的x、y和随机的纬度、经度数值,输出结果并切换控制等。

我已经对代码进行了相当多的测试,结果不是100%完美的,将altitude调高一些,比如50米,可以解决这个问题,但显然会减少可能选择的坐标区域。

显然,您可以根据自己的需求来调整这个想法。也许可以运行代码多次来填充数据库或其他内容。


我认为这是解决问题的一种新颖方式。 - sdolgy

3

作为资源,你肯定需要一张地图。你可以在这里获取:http://www.naturalearthdata.com/

然后,我会准备一个1比特的黑白位图资源,用1表示陆地,用0表示水域。

位图的大小取决于您需要的精度。如果您需要5度,则位图将是360/5 x 180/5 = 72x36像素=2592位。

然后,我会在Java中加载此位图,在上述范围内生成随机整数,读取位,并在其为零时重新生成。

P.S. 您还可以在这里查找一些现成的解决方案:http://geotools.org/


2
他要求5个小数点的精度,这是1,603,314,989,500,000位,所以不确定这种精度是否可行。 - philwb

2

作为备选方案,也许您可以选择一个随机的国家,然后在这个国家内随机选择一个坐标。在选择国家时,可以使用其面积作为权重来公平地选择。


1

这里有一个库链接,你可以使用它的.random()方法获取一个随机坐标。然后你可以使用GeoNames WebServices来确定它是否在陆地上。它们有一系列的网络服务,你只需要使用正确的那个就可以了。GeoNames是免费而可靠的。



0

补充一下bsimic所说的挖掘GeoNames Webservices的方法,这里有一个捷径:
他们有一个专门用于请求海洋名称的WebService

(我知道OP由于请求量的限制而不能使用公共Web服务。尽管如此,我在寻找同样基本问题时偶然发现了这个,认为这很有帮助。)

前往http://www.geonames.org/export/web-services.html#astergdem并查看“海洋/反向地理编码”。它可用作XML和JSON格式。创建一个免费用户帐户以避免演示帐户的每日限制。

关于海洋区域的请求示例(波罗的海,JSON-URL):

http://api.geonames.org/oceanJSON?lat=54.049889&lng=10.851388&username=demo

结果是

{
  "ocean": {
    "distance": "0",
    "name": "Baltic Sea"
  }
}

尽管一些陆地坐标会导致

{
  "status": {
    "message": "we are afraid we could not find an ocean for latitude and longitude :53.0,9.0",
    "value": 15
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接