gis地图鼠标点击命中要素研究

背景

现在我有一个提供mvt矢量切片的服务、一个基于mapboxjs的gis展示页面。
想要做一个功能:在浏览地图时,用户鼠标点击地图上某个位置后,发起一个查询请求,该请求需要在地图服务端查询是否命中某些个feature?如果命中,则返回命中的位于最上层的一个feature,否则返回空。

思路

  1. 最开始的思路是在请求中传入 x,y经纬度参数,服务端接收后作为一个point去判断这个point与某些feature的包含关系。
    结果:经过测试,不可取。
    原因:这样忽略了用户当前所处 zoom(地图缩放等级) 和 硬件 pixel(像素值)的因素,会导致你传过去的这个point始终都是在 最大zoom比如 22下的一个判断,这样当用户将 zoom 缩扩到 10+ 或其他值下,本该一个鼠标点击就可以命中那个feature的,而实际却没有。
    假设地图上有一个 “惠” 字,假设我在不同zoom下总能用鼠标命中同一个位置(x,y),那么在不同zoom下请看效果:
  • zoom=22时:明显无法命中该字


    image.png
  • zoom=16时:明显可以命中该字


    image.png
  1. 要解决思路1的问题,需要 引入 zoom 和 pixSize 共同将请求中的point 换算扩展成适当的 bbox ,再去判断空间相交关系即可。

解决:

已知:赤道处:y=0°纬度值
地球周长:40075016 米
切片规格:512*512,即 (29)2
计算 y=0,zoom=0处的“米每像素”的值,设为 metersPerPixel?
公式为:
var metersPerPixel = function(latitude, zoomLevel) {
var earthCircumference = 40075017;
var latitudeRadians = latitude * (Math.PI/180);
return earthCircumference * Math.cos(latitudeRadians) / Math.pow(2, zoomLevel + 9);
};

已知屏幕分辨率19201080,鼠标在浏览器上手抓的大小固定为20pix20pix大小,所以这里 pixValue = 20;
计算 y=0,zoom=7处的“鼠标抓取范围边长”的值?
在 y=0,zoom=7时,metersPerPixel = 611,所以 边长为 20 * 611 = 12220 米;
计算 y=34.578915,zoom=7处的“鼠标抓取范围边长”的值?
在 y=34.578915,zoom=7时,metersPerPixel = 503.654,所以 边长为 20 * 503.654 = 10073.1 米;

image.png

上图赤道处,米每像素的值,可以参考 https://docs.mapbox.com/help/glossary/zoom-level/
可以试着计算非赤道处的该值;
可以试着计算 256256 切片规格下的该值,256256,即 (28)2;
如果在手机上浏览,可以试着将pixValue改为手指触碰后抓取范围,然后计算看看。

java代码:

proj4j

<dependency>
            <groupId>org.osgeo</groupId>
            <artifactId>proj4j</artifactId>
            <version>0.1.0</version>
        </dependency>

Proj4jUtil


import org.osgeo.proj4j.*;

/**
 * @ClassName Proj4jUtil
 * @Author ckk
 * @Date 2024/1/9 13:38
 */
public class Proj4jUtil {

    public static CRSFactory factory = new CRSFactory();
    public static CoordinateReferenceSystem sourceCRS4326 = factory.createFromName("EPSG:4326"); // WGS84坐标系统
    public static CoordinateReferenceSystem targetCRS3857 = factory.createFromName("EPSG:3857"); // EPSG:3857坐标系统

    public static CoordinateTransformFactory ctFactory = new CoordinateTransformFactory();
    public static CoordinateTransform tranform4326To3857 = ctFactory.createTransform(sourceCRS4326, targetCRS3857);
    public static CoordinateTransform tranform3857To4326 = ctFactory.createTransform(targetCRS3857, sourceCRS4326);

    public static ProjCoordinate convertTo3857(double longitude, double latitude) {
        ProjCoordinate transform = tranform4326To3857.transform(new ProjCoordinate(longitude, latitude), new ProjCoordinate());
        return transform;
    }

    public static ProjCoordinate convertTo4326(double x, double y) {
        ProjCoordinate transform = tranform3857To4326.transform(new ProjCoordinate(x, y), new ProjCoordinate());
        return transform;
    }

//    public static void main(String[] args) {
//        ProjCoordinate projCoordinate = Proj4jUtil.convertTo3857(117.10150553692836, 34.3724595227);
//        ProjCoordinate projCoordinate1 = Proj4jUtil.convertTo4326(13035679.96749657, 4078924.561071809);
//        System.out.println("11");
//    }
}

MapBoxVectorTileUtil


import org.osgeo.proj4j.ProjCoordinate;

import java.util.HashMap;
import java.util.Map;

/**
 * 缩放级别决定了在地图上可以看到多少世界。Mapbox提供23个缩放级别的地图,其中0是最低的缩放级别(完全缩小),22
 * 是最高的(完全放大)。在低缩放级别时,一小套地图瓷砖覆盖了大片地理区域。在更高的缩放级别,更多的瓷砖覆盖了较小的地理区域。
 * @ClassName a
 * @Author ckk
 * @Date 2023/12/22 13:36
 */
public class MapBoxVectorTileUtil {

    public final static int earthCircumference = 40075016;
    public final static int tileSize = 512;

    /**
     * 计算点击处的抓取范围,默认返回 3857 投影下的范围
     * 默认支持 512*512 切片尺寸的gis画布,如需扩展要更改 tileSize 参数
     * @param x 经度
     * @param y 纬度
     * @param zoom 缩放级别
     * @param pixSize 像素大小(抓取工具的像素大小)
     * @param output4326 是否返回 4326 投影下的范围,默认为 false
     * @return
     */
    public static String logLat2pixelBbox(double x, double y, double zoom, int pixSize, boolean output4326){
        // 计算 米每像素
        double metersPerPixel = metersPerPixel(y, zoom);

        // 4326转3857
        ProjCoordinate projCoordinate = Proj4jUtil.convertTo3857(x, y);

        // 计算外扩
        double extent = metersPerPixel * pixSize / 2;

        // 计算鼠标或触摸的抓取范围
        double minX = -extent + projCoordinate.x;
        double minY = -extent + projCoordinate.y;
        double maxX = extent + projCoordinate.x;
        double maxY = extent + projCoordinate.y;

        StringBuilder bbox = new StringBuilder();
        if(!output4326){
            // 构造bbox
            bbox.append("SRID=3857;POLYGON((");
            bbox.append(minX).append(" ").append(minY).append(", ");
            bbox.append(minX).append(" ").append(maxY).append(", ");
            bbox.append(maxX).append(" ").append(maxY).append(", ");
            bbox.append(maxX).append(" ").append(minY).append(", ");
            bbox.append(minX).append(" ").append(minY);
            bbox.append("))");
        }else{
            // 3857转4326
            ProjCoordinate leftBottom = Proj4jUtil.convertTo4326(minX, minY);
            ProjCoordinate leftTop = Proj4jUtil.convertTo4326(minX, maxY);
            ProjCoordinate rightTop = Proj4jUtil.convertTo4326(maxX, maxY);
            ProjCoordinate rightBottom = Proj4jUtil.convertTo4326(maxX, minY);

            // 构造bbox
            bbox.append("SRID=4326;POLYGON((");
            bbox.append(leftBottom.x).append(" ").append(leftBottom.y).append(", ");
            bbox.append(leftTop.x).append(" ").append(leftTop.y).append(", ");
            bbox.append(rightTop.x).append(" ").append(rightTop.y).append(", ");
            bbox.append(rightBottom.x).append(" ").append(rightBottom.y).append(", ");
            bbox.append(leftBottom.x).append(" ").append(leftBottom.y);
            bbox.append("))");
        }

        return bbox.toString();
    }

    /**
     * 计算y纬度和zoom下,米每像素的值
     * @param y 纬度
     * @param zoom 缩放级别
     * @return 米每像素
     */
    public static double metersPerPixel(double y, double zoom){
        double latitudeRadians = y * Math.PI / 180;
        double metersPerPixel = earthCircumference * Math.cos(latitudeRadians) / (Math.pow(2, zoom) * tileSize);
        return metersPerPixel;
    }

//    public static void main(String[] args) {
//        // 测试 19.137666927404464 下能够命中 惠河
//        String bbox = logLat2pixelBbox(117.10702183509386, 34.371835846018826, 19.137666927404464, 20, false);
//        System.out.println(bbox);
//
//        // 测试 20.482818424170375 下不能够命中 惠河
//        String bbox1 = logLat2pixelBbox(117.10702183509386, 34.371835846018826, 20.482818424170375, 20, false);
//        System.out.println(bbox1);
//    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容