bcabchappy
路人甲
路人甲
  • 注册日期2013-05-04
  • 发帖数8
  • QQ
  • 铜币114枚
  • 威望2点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
阅读:3156回复:5

dpi/分辨率导致地图偏移问题

楼主#
更多 发布于:2017-10-13 12:30
arcgis 中同一份数据发两个地图服务,都进行切片,使用同样的比例尺和等级,但dpi不同,导致发布后的服务分辨率不同,使用wmts或ArcGISTiledMapServiceLayer加载叠加时会出现偏移,这种偏移通过代码可以纠正么?如何来操作。
喜欢0 评分0
gis
gis
管理员
管理员
  • 注册日期2003-07-16
  • 发帖数15945
  • QQ554730525
  • 铜币25337枚
  • 威望15352点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
  • 帝国沙发管家
  • GIS帝国明星
  • GIS帝国铁杆
1楼#
发布于:2017-10-13 14:54
建议使用一样的dpi重新发布吧
举报 回复(1) 喜欢(0)     评分
bcabchappy
路人甲
路人甲
  • 注册日期2013-05-04
  • 发帖数8
  • QQ
  • 铜币114枚
  • 威望2点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
2楼#
发布于:2017-10-13 14:57
gis:建议使用一样的dpi重新发布吧回到原帖
现在对接其它部门的地图服务,对方的dpi是90.71,我们的默认是96,测试发现偏移应该是由dpi不一致导致的,重新改dpi发布工作量有些大,想着看能不能在代码这边处理下
举报 回复(0) 喜欢(0)     评分
gis
gis
管理员
管理员
  • 注册日期2003-07-16
  • 发帖数15945
  • QQ554730525
  • 铜币25337枚
  • 威望15352点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
  • 帝国沙发管家
  • GIS帝国明星
  • GIS帝国铁杆
3楼#
发布于:2017-10-13 15:06
根据地图范围,算出1像素代表的实际大小和范围,然后做一些偏移,可能可以解决一些问题。
参考一下:

 ArcGIS产品,包括桌面产品、Web APIs、Native SDKs都提供了对WMTS的支持。如此,可以通过这些接口来访问天地图的WMTS服务。但是实际情况要复杂一些,经过测试发现,使用ArcGIS的WMTS接口访问天地图,会出现偏差,如下图所示。



经过研究发现,产生偏差的根本原因在于:ArcGIS WMTS接口中使用的DPI与天地图使用的DPI不一致。



OGCWMTS标准中规定,通过getcapatilities请求可以获得WMTS的元数据。上图是天地图2.0 WMTS元数据的部分截图(XML格式)。元数据中包含各个级别的比例尺数据(如图中红框内容)。在访问WMTS时,需要通过这些元数据计算出分辨率,公式如下所示。







OGC WMTS规范中DPI采用90.71(即采用0.028mm作为一个像素的物理宽度),而天地图使用的DPI采用国家标准规定的96(见《电子地图规范》)。由于ArcGIS WMTS接口实现均遵循OGC WMTS标准,使用90.71作为DPI来计算分辨率,导致ArcGIS通过WMTS接口访问天地图时,图片物理尺寸变大,使得地图看上去向右下方偏移。



2.  扩展ArcGIS接口访问天地图(以ArcGIS Runtime SDK forAndroid为例)


在第2小结,分析了用ArcGIS WMTS接口访问天地图产生偏移的原因,那么就可以有针对性的对ArcGIS接口进行扩展,来实现对天地图的访问。ArcGIS接口可以扩展。以ArcGIS Runtime SDK for Android为例,提供了TiledServiceLayer类。这是访问切片服务的基础类,通过扩展这个类,就可以访问天地图的WMTS服务了。扩展之前,需要了解一下天地图服务的一些参数,包括:

(1)比例尺


[table=100%][tr][td]
// 两种坐标系下的分辨率一致
private static final double[] SCALES = { 2.958293554545656E8,
            1.479146777272828E8, 7.39573388636414E7, 3.69786694318207E7,
            1.848933471591035E7, 9244667.357955175, 4622333.678977588,
            2311166.839488794, 1155583.419744397, 577791.7098721985,
            288895.85493609926, 144447.92746804963, 72223.96373402482,
            36111.98186701241, 18055.990933506204, 9027.995466753102,

             4513.997733376551, 2256.998866688275 };[/td][/tr][/table]



(2)分辨率

// 墨卡托坐标系下的分辨率
private static final double[] RESOLUTIONS_MERCATOR = { 78271.51696402048,
            39135.75848201024, 19567.87924100512, 9783.93962050256,
            4891.96981025128, 2445.98490512564, 1222.99245256282,
            611.49622628141, 305.748113140705, 152.8740565703525,
            76.43702828517625, 38.21851414258813, 19.109257071294063,
            9.554628535647032, 4.777314267823516, 2.388657133911758,
            1.194328566955879, 0.5971642834779395 };
// 国家2000坐标系下的分辨率
private static final double[] RESOLUTIONS_2000 = { 0.7031249999891485,
            0.35156249999999994, 0.17578124999999997, 0.08789062500000014,
            0.04394531250000007, 0.021972656250000007, 0.01098632812500002,
            0.00549316406250001, 0.0027465820312500017, 0.0013732910156250009,
            0.000686645507812499, 0.0003433227539062495,
            0.00017166137695312503, 0.00008583068847656251,
            0.000042915344238281406, 0.000021457672119140645,

             0.000010728836059570307, 0.000005364418029785169};




(3)起始点


// 国家2000坐标系下的起始点
private static final Point ORIGIN_2000 =new Point(-180, 90);
// 墨卡托坐标系下的起始点
private static final Point ORIGIN_MERCATOR =new Point(-20037508.3427892,

             20037508.3427892);




(4)地图范围


// 国家2000坐标系下的地图范围
private static final double X_MIN_2000 = -180;
private static final double Y_MIN_2000 = -90;
private static final double X_MAX_2000 = 180;
private static final double Y_MAX_2000 = 90;
// 墨卡托坐标系下的地图范围
private static final double X_MIN_MERCATOR = -20037508.3427892;
private static final double Y_MIN_MERCATOR = -20037508.3427892;
private static final double X_MAX_MERCATOR = 20037508.3427892;

private static final double Y_MAX_MERCATOR = 20037508.3427892;






有了以上信息,通过扩展TiledServiceLayer,就可以访问天地图了,核心代码如下所示:TianDiTuLayer.java



public class TianDiTuLayer extends TiledServiceLayer {
   private TianDiTuLayerInfolayerInfo;
   public TianDiTuLayer(int layerType) {
        super(true);
        this.layerInfo = LayerInfoFactory.getLayerInfo(layerType);
        this.init();
   }
   private void init() {
        try {
            getServiceExecutor().submit(new Runnable() {
                 publicvoid run() {
                     TianDiTuLayer.this.initLayer();
                 }
            });
        } catch (RejectedExecutionException rejectedexecutionexception) {
            Log.e("ArcGIS","initialization of the layer failed.",
                     rejectedexecutionexception);
        }
   }
   protected byte[] getTile(int level,int col, int row)throws Exception {
        if (level >layerInfo.getMaxZoomLevel()
                 || level < layerInfo.getMinZoomLevel())
            return new byte[0];
        String url = layerInfo.getUrl()
                 + "?service=wmts&request=gettile&version=1.0.0&layer="
                 + layerInfo.getLayerName() +"&format=tiles&tilematrixset="
                 + layerInfo.getTileMatrixSet() +"&tilecol=" + col
                 + "&tilerow=" + row +"&tilematrix=" + (level+1);
        Map<String, String> map = null;
        return com.esri.core.internal.io.handler.a.a(url, map);
   }
   protected void initLayer() {
        if (getID() == 0L) {
            nativeHandle = create();
            changeStatus(com.esri.android.map.event.OnStatusChangedListener.STATUS
                     .fromInt(-1000));
        } else {
            this.setDefaultSpatialReference(SpatialReference.create(layerInfo
                     .getSrid()));
            this.setFullExtent(new Envelope(layerInfo.getxMin(),layerInfo
                     .getyMin(), layerInfo.getxMax(),layerInfo.getyMax()));
            this.setTileInfo(new TileInfo(layerInfo.getOrigin(),layerInfo
                     .getScales(), layerInfo.getResolutions(),layerInfo
                     .getScales().length,layerInfo.getDpi(), layerInfo
                     .getTileWidth(), layerInfo.getTileHeight()));
            super.initLayer();
        }
   }

}




以下代码说明如何使用扩展后的TianDiTuLayer来显示天地图服务。

mapMercator = (MapView) this.findViewById(R.id.mapMercator);
Layer mapLayer = new TianDiTuLayer(TianDiTuLayerTypes.TIANDITU_VECTOR_MERCATOR);
this.mapMercator.addLayer(mapLayer);
Layer annotationLayer = new TianDiTuLayer(
        TianDiTuLayerTypes.TIANDITU_VECTOR_ANNOTATION_CHINESE_MERCATOR);
this.mapMercator.addLayer(annotationLayer);
使用扩展后的TianDiTuLayer加载天地图,与业务数据叠加效果图如下所示:







3.  总结

ArcGIS接口可以灵活扩展。以上是以ArcGIS Runtime SDK for Android为例说明如何扩展来加载天地图。其它接口,比如Web APIs、Native SDKs、Portal for ArcGIS、桌面都可以通过类似的方式实现扩展。



想扩展源码及示例,包括使用说明文档, 请点击此处下载
举报 回复(0) 喜欢(0)     评分
gis
gis
管理员
管理员
  • 注册日期2003-07-16
  • 发帖数15945
  • QQ554730525
  • 铜币25337枚
  • 威望15352点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
  • 帝国沙发管家
  • GIS帝国明星
  • GIS帝国铁杆
4楼#
发布于:2017-10-13 15:09


ArcGIS 切图属性之DPI详解


在ArcGIS切图的属性设置中,有一个不起眼的属性我们一直没有怎么注意——DPI,关于DPI官方帮助文档历来都是一笔带过,一般就是说默认的96DPI足够了,不用调整,这个属性我们也是建议用户不调整的。那么这个属性到底有什么用呢?
在开始之前,我们先得了解什么是DPI (DPI的wikipedia定义)。首先我们需要明确的一点是:DPI是和打印相关的属性,离开了打印这个需求,DPI这个属性是没有存在的意义的。
关于DPI我们需要理解,它定义了在一英寸(2.54cm)的距离上,打印机所打印的点数,所以它是一个密度单位(类似于速度),DPI跟你打印多大的尺寸没有关系。
DPI说完了,我们再来看另外一个非常容易和DPI混淆的概念——显示器的像素(pixel)。显示器的像素是显示的最小显示单位,一个像素内只能显示一种颜色,在32位真色彩下这个颜色有 2的32次方种选择,所以我们在电脑屏幕上看到的画面还是非常丰富多彩的,参考下图以助于理解,10*10个pixel就能较完整的显示一个圆了。

我们常见的屏幕分辨率 1920*1080,1200*800、图像的大小256*256 等等等等,这些数字代表的都是pixel,pixel是电脑上显示的元单位。
好了,在开始我们的ArcGIS切片之前,先温习一下上面两个概念,然后心中默念100遍“DPI和 pixel木有关系,木有一毛钱关系”,然后开始我们的切片之旅吧!
理解一个属性最好的方法就是对比了,我们先来发个中国地图服务:

同一个地图文档,我们发布两个地图服务,一个叫china96(准备切96dpi的切片),一个叫china192(准备切192dpi的切片),切片的具体属性如下:

另外一个 96dpi的属性就不贴了,除了dpi,两者的其他切片属性都是一样的(4个级别 64,000,000 到8,000,000,切片格式jpg,质量75,离散型切片)
下面我们去缓存目录看看生成的缓存有什么不同:

先来看看切片的根目录,因为两者都是切了一模一样的scale,所以都是4级切片目录,木有错,然后我们再深入一点…进入L00级:

进去以后,我们发现192dpi的缓存数量比96dpi的要多很多(坑爹!)再进入下一级(row)

好吧,我们看到L00 比例尺下 ,96dpi的切片个数是 2*3=6个(大小都是256*256的切片…),192dpi的切片个数是 4*4=16个。是不是完全纠结了……比例尺一样的情况下,192的切片拼起来能比96的切片拼起来大这么多……
先放下纠结,回到之前默念100遍的东西上:图片大小(width, height, pixel) 和DPI 木有关系!也就是说图片在电脑上是什么样的,和它打印出来是什么样的是两码事。那么我们怎么能增大图片的DPI呢?
之前已经说过pixel是图片在电脑上显示的元单位,也是jpg、png等栅格类型的图像格式的元单位,它不能在被细分了,我们假设1个pixel对应着打印机的一个dot(好吧,这里确实是有那么点关系,实际情况可能不是1:1的对应关系,为了方便理解。)那么我们要增大DPI一倍,只能多画一倍的点,然后规定这张图片打印出来还是原来之前那么长(从这个逻辑来说图片的像素和DPI没有关系,DPI只是图片的一个附加属性,它定义了图片打印时应遵守的行为)。说道这里可能会有问题了,为啥192dpi的切片不是96dpi切片的4被呢,它们不应该是长宽都为1:2的关系吗?
我们先来看下面这张图片:

虽然这么看不是很准确,但是左边(96dpi)中国的部分,是不是相当于右边(192dpi)中国部分的二分之一?之所以造成96 vs 192 不是准确的4被关系,原因是左右两边的tiling schema(切图方案) 除了dpi,其他属性是完全一样的,即可以想象成切片的网格是不变的,你把中国地图放大一倍,中国这个要素(准确的说是中国这个要素的Extent)所覆盖的切片数就不一定是准确的4被关系了~~
为了进一步验证这个猜想,我们再来看96dpi的L01级切片和192dpiL00级切片的对比:


这两张图信息量有点大,我稍微解释一下,把96dpi的放大一倍,结果切片就跟192dpi上一级切片完全吻合了!可以看到里面图片的属性都是一模一样的,具体属性我就不截图的,他们图片的唯一区别就是,左边图片的dpi是 96,右边图片的dpi是192,这有更加验证了dpi只是图片的附加属性,完全不影响图像二进制栅格中所存储的值。
好,下面我们来干一个更加纠结的是,把这两个缓存都加入ArcMap中:

我们可以看到,肉眼来看,两个切片是完全一样的(无视96dpi的头顶部分,漏了一块切片。。。),那么ArcMap是怎么达到这个效果的呢,为了保证比例尺一定,即图片大小一致,那么ArcMap只能压缩192dpi的切片大小了(压小3/4)。压缩图片大小这个本事,Web API 可没有了,所以API 加载192dpi的缓存地图的话,会把比例尺放大一倍……
在来看看Rest API 的出图以加深理解:

我们限定了export map的出图大小400*400,然后一个出96dpi,一个出192dpi,可以看到限定了切片拼起来的总大小,限定了要显示的空间范围extent,那我这个 192dpi的大图怎么显示全图? 没有办法,只能缩小比例尺了(增大分母),为啥右图中的symbol,label会显得这么大呢? 因为我们知道 symbol,label是独立与比例尺的,我们把右图的比例尺缩小,要素可以缩小,但是动态标注的label,symbol大小是不会变的,故而会显得变大了。
举报 回复(0) 喜欢(0)     评分
bcabchappy
路人甲
路人甲
  • 注册日期2013-05-04
  • 发帖数8
  • QQ
  • 铜币114枚
  • 威望2点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
5楼#
发布于:2017-12-18 15:34
谢谢你,正是通过这两篇文章,再进一步测试,确认问题原因的
举报 回复(0) 喜欢(0)     评分
游客

返回顶部