精华内容
下载资源
问答
  • 本篇文章已授权微信...通过该功能,可通过缩小地图层级,将定义范围内的多个标注点,聚合显示成一个标注点,解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。 本demo 修改算法流程: 加入异步添加屏...
        

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

    老规矩先上图
    最近 没有什么时间,后面项目再补上详细说明

    下载.gif

    百度地图SDK新增点聚合功能。通过该功能,可通过缩小地图层级,将定义范围内的多个标注点,聚合显示成一个标注点,解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。

    本demo 修改算法流程:
    1. 加入异步添加屏幕上图片,

    2. 只加载屏幕范围内的图片

    3. 优化渲染逻辑
      大大减少运算的时间(经过测试1W张不同经纬度的图片 300-500ms 可以计算完毕)

    讲解点聚合功能,整个分析过程分为三部分:

    1、如何添加点聚合功能到项目中;

    2、整体结构分析;

    3、核心算法分析。

    一、添加点聚合功能

    如官网所示,添加点聚合的方法分为三步:
    1、声明点聚合管理类为全局变量,并初始化。核心代码如下图:

    MarkerOptions opts = new MarkerOptions().position(cluster.getPosition())
                .icon(BitmapDescriptorFactory.fromBitmap(XX));
    Marker marker = (Marker) mMap.addOverlay(opts);
    二、整体结构分析
    先上一个思维导图:

    思维导图

    如上图,点聚合有四个类

    1、Cluster数据:主要是聚合后的数据类型

    2、四叉树:记录初始范围内的所有图片并以四叉树的数据结构组织。核心算法需要用到的数据结构,后面再讲;

    3、点聚合算法:基于四叉树的核心算法。后面讲;

    4、Cluster管理:对外接口,通过调用核心算法实现点聚合功能、

    整个功能的主要流程有两条:

    1、添加item:Cluster管理类添加item接口 算法类添加item接口:记录所有的图片信息 四叉树类添加item接口:已四叉树的结构记录所有图片信息

    2、获取聚合后的集合:Cluster管理类获取聚合接口 算法类核心算法接口:通过核心算法获取聚合后的集合

    三、核心算法

    首先要说一个概念:世界宽度。
    百度地图是把整个地球是按照一个平面来展开,并且通过墨卡托投影投射到xy坐标轴上面。上图:

    世界地图

    墨卡托投影后的坐标轴

    很明显墨卡托投影把整张世界地图投影成

    X∈ [0,1] ; Y∈ [0,1]。

    的一个正方型区域。
    X 表示的是经度,Y表示的是纬度。

    (其实确认来说是投影一个上下无限延伸的长方体,只是Y属于[0,1]这个范围已经足够我们使用)上图说明:

    BG}`)1PW56(T@3K9QSDL4_E.png

    从上面看出 -85°的纬度对应Y坐标是1,那么-90°呢,你们自己可以去算一下,是+∞ (正无穷)。

    至于为什么讲这个,因为计算搜索范围的时候,所有的经纬度都需要换算成Point 来计算,是不是很方便性,而且不易出错。
    真是感叹伟人的强大!

    附注
    转换的公式在下面这个类里面:
    SphericalMercatorProjection.java
    接下来说说如何通过四叉树组织数据

    四叉树的基本思想是把空间递归划分为不同层次的树结构。它把已知的空间等分成四个相等的子空间,如此递归下去,直到满足当层数目量超过50,或者层级数大于40则停止分割。示意图如下:

    fAtyhsYUkslj (1).png

    OK,接下来说说具体流程

    1. 遍历QuadItem,只加载屏幕内的点,生成四叉树,方便搜索。

    2. 如果图片已被visitedCandidate记录,则continue下面步骤,直到需要处理的图片没有被visitedCandidates记录;

    3. 对上一次屏幕上的点QuadItem先进行处理;

    4. 根据MAX_DISTANCE_IN_DP及图片位置计算出searchBounds;

    5. 通过四叉树得到searchBounds内所有的图片;

    6. 如果图片数量为1,记录并跳到步骤2;

    7. 遍历得到的图片;

    8. 依次对得到的图片进行处理,

    9. 如果图片到中心点的距离比distanceToCluster(此图片与包含此图片的前cluster的距离)小,把图片加入结果集,并移除前Cluster拥有该图片的引用,并记录此次更小的距离,跳步骤8继续遍历剩余项。

    重点源码分析:
    1.聚合触发口
    ClusterManager.java
    @Override
        public void onMapStatusChangeFinish(MapStatus mapStatus) {
            if (mRenderer instanceof BaiduMap.OnMapStatusChangeListener) {
                ((BaiduMap.OnMapStatusChangeListener) mRenderer).onMapStatusChange(mapStatus);
            }
    
            // 屏幕缩放范围太小,不进行触发聚合功能
            if (mPreviousCameraPosition != null
                    && Math.abs((int) mPreviousCameraPosition.zoom - (int) mapStatus.zoom) < 1
                    && mPreviousCameraPosition.target.latitude == mapStatus.target.latitude
                    && mPreviousCameraPosition.target.longitude == mapStatus.target.longitude) {
                return;
            }
           //记录
            mPreviousCameraPosition = mapStatus;
         
            //算法运算,计算出聚合后结果集,并且addMarker 到屏幕上
            cluster(mapStatus.zoom,mapStatus.bound);
        }

    对地图进行手势操作,都会进行触发这个函数,并进行聚合操作

    2.算法运算
    NonHierarchicalDistanceBasedAlgorithm.java
    @Override
        public Set<Cluster<T>> getClusters(double zoom, LatLngBounds visibleBounds) {
        ...
        }

    这个函数有点多,不过在github 上面的demo 已经注释满满,请移步github 查看。

    3.渲染UI(addMarker) 
    class DefaultClusterRenderer {
        class CreateMarkerTask {
           ...
        }
    }
    private void perform(MarkerModifier markerModifier) {
                // Don't show small clusters. Render the markers inside, instead.
                markRemoveAndAddLock.lock();
                //真正添加Marker 的地方
    
                Marker marker = mClusterToMarker.get(cluster);
                if (marker == null || (marker != null
                        && mMarkerToCluster.get(marker).getSize() != cluster.getSize())) {
                    //异步加载占时不添加Marker
                    Integer size = onReadyAddCluster.get(cluster);
                    if (size == null || size != cluster.getSize()) {
                        onReadyAddCluster.put(cluster,cluster.getSize());
                        onBeforeClusterRendered(cluster, new MarkerOptions()
                                .position(cluster.getPosition()));
    
                    }
                }
                markRemoveAndAddLock.unlock();
                newClusters.add(cluster);
    
            }

    主要添加图片的是onBeforeClusterRendered 这一个函数, 我们看一下实现:

    public class PersonRenderer extends DefaultClusterRenderer<LocalPictrue> {
      DataSource<CloseableReference<CloseableImage>> target = cancleMap1.get(cluster);
            if(target != null) {
                target.close();
                cancleMap1.remove(target);
            }
    
    
            final LocalPictrue person = cluster.getItems().iterator().next();
    
            ImageRequest imageRequest = ImageRequestBuilder
                    .newBuilderWithSource(Uri.fromFile(new File(person.path)))
                    .setProgressiveRenderingEnabled(false)
                    .setResizeOptions(new ResizeOptions(50, 50))
                    .setPostprocessor(new BadgViewPostprocessor(mContext,cluster))
                    .build();
    
            ImagePipeline imagePipeline = Fresco.getImagePipeline();
            DataSource<CloseableReference<CloseableImage>> dataSource =
                    imagePipeline.fetchDecodedImage(imageRequest,mContext);
    
            dataSource.subscribe(new BaseBitmapDataSubscriber() {
    
                @Override
                public void onNewResultImpl(@Nullable Bitmap bitmap) {
                    // You can use the bitmap in only limited ways
                    // No need to do any cleanup.
                    if(bitmap != null && !bitmap.isRecycled()) {
                        //you can use bitmap here
                        setIconByCluster(person.path,cluster,
                                markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)));
                    }
                    cancleMap1.remove(cluster);
                }
    
                @Override
                public void onFailureImpl(DataSource dataSource) {
                    // No cleanup required here.
                    System.out.println("shibai");
                }
    
            }, UiThreadImmediateExecutorService.getInstance());
    
            cancleMap1.put(cluster, dataSource);
    
    }

    很明显我这边解决了 baiduMap 在UI线程上添加图片阻塞问题, 添加强大的 fresco 第三方加载库,进行异步加载图片,接下来看图片下载完成后 执行setIconByCluster 函数:

    //异步回调回来的icon ,需要
        public void setIconByCluster(String path, Cluster<T> cluster, MarkerOptions markerOptions) {
            markRemoveAndAddLock.lock();
            Integer size = onReadyAddCluster.get(cluster);
            if (size != null && cluster.getSize() == size) {
                Marker marker = mClusterToMarker.get(cluster);
                if (marker != null) {
         //如果该图在屏幕上已经打了marker,那么替换icon即可,主要解决图片重新加载闪烁问题    
                  marker.setIcon(markerOptions.getIcon());
                } else {
                //打入新的Marker
                    marker = mClusterManager.getClusterMarkerCollection().addMarker(markerOptions);
                }
    
                mMarkerToCluster.put(marker, cluster);
                mClusterToMarker.put(cluster, marker);
            }
            markRemoveAndAddLock.unlock();
        }
    总结:

    重点源码分析,基本上到这里结束。我们来撸一撸流程:

    1. 通过onMapStatusChangeFinish回调,去执行点聚合运算;

    2. 通过 getClusters把聚合后的结果集算出来;

    3. 通过CreateMarkerTask.perform() 把 marker打到屏幕上。

    备注:

    更多细节请看源代码,
    喜欢去帮忙start一下,谢谢!

    github:
    [https://github.com/zhangchaoj...

    展开全文
  • 高仿ios相册地图功能

    2017-08-23 08:00:03
    今日科技快讯 ...8月22日,据美国媒体报道,谷歌当日公布Android O系统的名称为Android Oreo(奥利奥)。此外,谷歌也将最新的源代码推送到Android...Android Oreo提供了一系列新功能,包括画中画、自

    640?wx_fmt=png

    今日科技快讯

    8月22日,据美国媒体报道,谷歌当日公布Android O系统的名称为Android Oreo(奥利奥)。此外,谷歌也将最新的源代码推送到Android开源项目(AOSP)。Android Oreo提供了一系列新功能,包括画中画、自动填写API、自动调节尺寸文本浏览、自适应图标、通知标志、高品质蓝牙音频解码和更严的应用安装控制。Android 8.0的亮点还包括重新设计的通知系统(包括视觉和功能方面的改进)、电池续航时间以及多屏幕支持等。而Oreo系统还将在启动启动速度和内存管理上都有很大提升,在Pixel手机上启动时间可以提升100%。另外,系统资源和后台任务管理的效率都有很大改善。

    作者简介

    本篇来自 Override 的投稿,分享了一个高仿ios相册地图的功能,希望能对大家有所帮助。

    Override 的博客地址:

    http://www.jianshu.com/u/75711cf32043

    前言

    老规矩先上图, 高仿 ios 相册, 地图算法分析。

    640?wx_fmt=gif

    下载.gif

    正文

    百度地图 SDK 新增点聚合功能。通过该功能,可通过缩小地图层级,将定义范围内的多个标注点,聚合显示成一个标注点,解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。

    基于百度地图优化算法流程:(其实百度也是抄袭 google map 的算法)

    1. 加入异步添加屏幕上图片

    2. 只加载屏幕范围内的图片

    3. 优化渲染逻辑,大大减少运算的时间
      (经过测试 2W 张不同经纬度的图片 300-500ms 可以计算完毕,绝对比 ios 相册还快。)

    讲解点聚合功能,整个分析过程分为三个部分:

    1. 如何添加点聚合功能到项目中;

    2. 整体结构分析;

    3. 核心算法分析。

    • 添加聚合功能

    如官网所示,添加点聚合的方法分为三步:声明点聚合管理类为全局变量,并初始化。核心代码如下图:

    640?wx_fmt=jpeg

    • 整体结构分析

    先上一个思维导图:

    640?wx_fmt=png

    2.png

    如上图,点聚合有四个类

    • Cluster 数据:主要是聚合数据类型,图示很明确,这里不罗嗦;

    • 四叉树:记录初始范围内的所有图片并以四叉树的数据结构组织。核心算法需要用到的数据结构,后面再讲;

    • 点聚合算法:基于四叉树的核心算法。后面讲;

    • Cluster 管理:对外接口,通过调用核心算法实现点聚合功能、

    整个功能的主要流程有两条:

    • 添加 item:Cluster 管理类添加 item 接口  算法类添加 item 接口:记录所有的图片信息  四叉树类添加 item 接口:已四叉树的结构记录所有图片信息

    • 获取聚合后的集合:Cluster 管理类获取聚合接口  算法类核心算法接口:通过核心算法获取聚合后的集合

    • 核心算法

    首先要说一个概念:世界宽度。
    百度地图是把整个地球是按照一个平面来展开,并且通过墨卡托投影投射到xy坐标轴上面。墨卡托投影链接地址:

    https://zh.wikipedia.org/wiki/%E9%BA%A5%E5%8D%A1%E6%89%98%E6%8A%95%E5%BD%B1%E6%B3%95

    上图:

    640?wx_fmt=png

    世界地图

    640?wx_fmt=png

    墨卡托头影后的坐标轴

    很明显墨卡托投影把整张世界地图投影成

    X∈ [0,1] ; Y∈ [0,1]。

    的一个正方型区域,世界宽度为 1 。
    X 表示的是经度,Y表示的是纬度。
    (其实准确来说是投影一个上下无限延伸的长方体,只是Y属于 [0,1] 这个范围已经足够我们使用)上图说明:

    640?wx_fmt=png

    3.png

    从上面看出  -85° 的纬度对应Y坐标是1,那么 -90° 呢,你们自己可以去算一下,是 +∞ (正无穷),90 ° 是 -∞ (负无穷) 。

    至于为什么讲这个,因为计算搜索范围的时候,所有的经纬度都需要换算成Point 来计算,是不是很方便性,而且不易出错。
    真是感叹伟人的强大!

    • 附注

    转换的公式在下面这个类里面:

    SphericalMercatorProjection.java

    接下来说说如何通过四叉树组织数据

    四叉树的基本思想是把空间递归划分为不同层次的树结构。它把已知的空间等分成四个相等的子空间,如此递归下去,直到满足当层数目量超过 50,或者层级数大于 40 则停止分割。示意图如下:

    640?wx_fmt=png

    四叉树

    OK,接下来说说具体流程

    • 遍历 QuadItem,只加载屏幕内的点,生成四叉树,方便搜索。

    • 如果图片已被 visitedCandidate 记录,则 continue 下面步骤,直到需要处理的图片没有被 visitedCandidates 记录;

    • 对上一次屏幕上的点QuadItem先进行处理;

    • 根据 MAX_DISTANCE_IN_DP 及图片位置计算出 searchBounds;

    • 通过四叉树得到 searchBounds 内所有的图片;

    • 如果图片数量为1,记录并跳到步骤2;

    • 遍历得到的图片;

    • 依次对得到的图片进行处理,

    • 如果图片到中心点的距离比 distanceToCluster (此图片与包含此图片的前cluster的距离)小,把图片加入结果集,并移除前 Cluster 拥有该图片的引用,并记录此次更小的距离,跳步骤 8 继续遍历剩余项。

    重点源码分析:

    • 聚合触发口

    ClusterManager.java

    640?wx_fmt=jpeg

    对地图进行手势操作,都会进行触发这个函数,并进行聚合操作

    • 算法运算

    NonHierarchicalDistanceBasedAlgorithm.java

    640?wx_fmt=jpeg

    这个函数有点多,不过在 github 上面的 demo 已经注释满满,请移步 github 查看。

    • 渲染 UI(addMaker)

    640?wx_fmt=jpeg

    640?wx_fmt=jpeg

    主要添加图片的是 onBeforeClusterRendered 这一个函数, 我们看一下实现:

    640?wx_fmt=jpeg

    很明显我这边解决了 baiduMap 在UI线程上添加图片阻塞问题, 添加强大的 fresco 第三方加载库,进行异步加载图片,接下来看图片下载完成后 执行setIconByCluster 函数:

    640?wx_fmt=jpeg

    总结

    重点源码分析,基本上到这里结束。我们来撸一撸流程:

    • 通过 onMapStatusChangeFinish 回调,去执行点聚合运算;

    • 通过 getClusters 把聚合后的结果集算出来;

    • 通过CreateMarkerTask.perform() 把 marker 打到屏幕上。

    备注

    更多细节请看源代码
    喜欢去帮忙start一下,谢谢!

    github 地址:

    https://github.com/zhangchaojiong/BaiduMapClusterSample/tree/master

    更多

    每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

    640?wx_fmt=gif

    如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

    欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

    640?wx_fmt=jpeg

    展开全文
  • 这是IOS上的地图相册,我在Android的手机上也见过类似的地图相册,好像华为自带的系统相册就有这个功能。 具体功能有: 1.可以根据照片获取到的经纬度信息,精确的在地图上显示。 2.在伸缩地图的时候,可以自动...
  • ol5里面实现相册地图

    千次阅读 2019-03-23 10:57:00
    如下图,在手机里面有一个这样的功能,我称之为“相册地图”,本文讲述的是通过扩展ol.style的类,来实现“相册地图”这个功能。 关键点 要实现这个功能有两个关键点:1、地图聚合;2、图片样式。有关地图聚类的在...

    概述

    如下图,在手机里面有一个这样的功能,我称之为“相册地图”,本文讲述的是通过扩展ol.style的类,来实现“相册地图”这个功能。
    相册地图

    关键点

    要实现这个功能有两个关键点:1、地图聚合;2、图片样式。有关地图聚类的在很早之前的文章里面已经涉及到过,所以本文重点讲述图片样式。

    实现效果

    相册地图

    选中后放大

    实现

    1、扩展ol.style.Photo
    /**
     * @classdesc
     * Set Photo style for vector features.
     *
     * @constructor
     * @param {} options
     *  @param { default | square | round | anchored | folio } options.kind
     *  @param {boolean} options.crop crop within square, default is false
     *  @param {Number} options.radius symbol size
     *  @param {boolean} options.shadow drop a shadow
     *  @param {ol.style.Stroke} options.stroke
     *  @param {String} options.src image src
     *  @param {String} options.crossOrigin The crossOrigin attribute for loaded images. Note that you must provide a crossOrigin value if you want to access pixel data with the Canvas renderer.
     *  @param {Number} options.offsetX Horizontal offset in pixels. Default is 0.
     *  @param {Number} options.offsetY Vertical offset in pixels. Default is 0.
     *  @param {function} options.onload callback when image is loaded (to redraw the layer)
     * @extends {ol.style.RegularShape}
     * @implements {ol.structs.IHasChecksum}
     * @api
     */
    ol.style.Photo = function(options)
    {	options = options || {};
      this.sanchor_ = options.kind=="anchored" ? 8:0;
      this.shadow_ = Number(options.shadow) || 0;
      if (!options.stroke)
      {	options.stroke = new ol.style.Stroke({ width: 0, color: "#000"})
      }
      var strokeWidth = options.stroke.getWidth();
      if (strokeWidth<0) strokeWidth = 0;
      if (options.kind=='folio') strokeWidth += 6;
      options.stroke.setWidth(strokeWidth);
      ol.style.RegularShape.call (this,
        {	radius: options.radius + strokeWidth + this.sanchor_/2 + this.shadow_/2,
          points:0
          //	fill:new ol.style.Fill({color:"red"}) // No fill to create a hit detection Image
        });
      // Hack to get the hit detection Image (no API exported)
      if (!this.hitDetectionCanvas_)
      {	var img = this.getImage();
        for (var i in this)
        {	if (this[i] && this[i].getContext && this[i]!==img)
        {	this.hitDetectionCanvas_ = this[i];
          break;
        }
        }
      }
    
      this.stroke_ = options.stroke;
      this.fill_ = options.fill;
      this.crop_ = options.crop;
      this.crossOrigin_ = options.crossOrigin;
      this.kind_ = options.kind || "default";
    
      this.radius_ = options.radius;
      this.src_ = options.src;
    
      this.offset_ = [options.offsetX ? options.offsetX :0, options.offsetY ? options.offsetY :0];
    
      this.onload_ = options.onload;
    
      if (typeof(options.opacity)=='number') this.setOpacity(options.opacity);
      if (typeof(options.rotation)=='number') this.setRotation(options.rotation);
      this.renderPhoto_();
    };
    ol.inherits(ol.style.Photo, ol.style.RegularShape);
    
    
    /**
     * Clones the style.
     * @return {ol.style.Photo}
     */
    ol.style.Photo.prototype.clone = function()
    {	return new ol.style.Photo(
      {	stroke: this.stroke_,
        fill: this.fill_,
        shadow: this.shadow_,
        crop: this.crop_,
        crossOrigin: this.crossOrigin_,
        kind: this.kind_,
        radius: this.radius_,
        src: this.src_,
        offsetX: this.offset_[0],
        offsetY: this.offset_[1],
        opacity: this.getOpacity(),
        rotation: this.getRotation()
      });
    };
    
    /**
     * Draws a rounded rectangle using the current state of the canvas.
     * Draw a rectangle if the radius is null.
     * @param {Number} x The top left x coordinate
     * @param {Number} y The top left y coordinate
     * @param {Number} width The width of the rectangle
     * @param {Number} height The height of the rectangle
     * @param {Number} radius The corner radius.
     */
    CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r)
    {	if (!r) this.rect(x,y,w,h);
    else
    {	if (w < 2 * r) r = w / 2;
      if (h < 2 * r) r = h / 2;
      this.beginPath();
      this.moveTo(x+r, y);
      this.arcTo(x+w, y, x+w, y+h, r);
      this.arcTo(x+w, y+h, x, y+h, r);
      this.arcTo(x, y+h, x, y, r);
      this.arcTo(x, y, x+w, y, r);
      this.closePath();
    }
      return this;
    }
    
    
    /**
     * Draw the form without the image
     * @private
     */
    ol.style.Photo.prototype.drawBack_ = function(context, color, strokeWidth)
    {	var canvas = context.canvas;
      context.beginPath();
      context.fillStyle = color;
      context.clearRect(0, 0, canvas.width, canvas.height);
      switch (this.kind_)
      {	case 'square':
        context.rect(0,0,canvas.width-this.shadow_, canvas.height-this.shadow_);
        break;
        case 'circle':
          context.arc(this.radius_+strokeWidth, this.radius_+strokeWidth, this.radius_+strokeWidth, 0, 2 * Math.PI, false);
          break;
        case 'folio':
          var offset = 6;
          strokeWidth -= offset;
          context.strokeStyle = 'rgba(0,0,0,0.5)';
          var w = canvas.width-this.shadow_-2*offset;
          var a = Math.atan(6/w);
          context.save();
          context.rotate(-a);
          context.translate(-6,2);
          context.beginPath();
          context.rect(offset,offset,w,w);
          context.stroke();
          context.fill();
          context.restore();
          context.save();
          context.translate(6,-1);
          context.rotate(a);
          context.beginPath();
          context.rect(offset,offset,w,w);
          context.stroke();
          context.fill();
          context.restore();
          context.beginPath();
          context.rect(offset,offset,w,w);
          context.stroke();
          break;
        case 'anchored':
          context.roundRect(this.sanchor_/2,0,canvas.width-this.sanchor_-this.shadow_, canvas.height-this.sanchor_-this.shadow_, strokeWidth);
          context.moveTo(canvas.width/2-this.sanchor_-this.shadow_/2,canvas.height-this.sanchor_-this.shadow_);
          context.lineTo(canvas.width/2+this.sanchor_-this.shadow_/2,canvas.height-this.sanchor_-this.shadow_);
          context.lineTo(canvas.width/2-this.shadow_/2,canvas.height-this.shadow_);break;
        default: /* roundrect */
          context.roundRect(0,0,canvas.width-this.shadow_, canvas.height-this.shadow_, strokeWidth);
          break;
      }
      context.closePath();
    }
    
    
    /**
     * @private
     */
    ol.style.Photo.prototype.renderPhoto_ = function()
    {
      var strokeStyle;
      var strokeWidth = 0;
      if (this.stroke_)
      {	strokeStyle = ol.color.asString(this.stroke_.getColor());
        strokeWidth = this.stroke_.getWidth();
      }
      var canvas = this.getImage();
    
      // Draw hitdetection image
      var context = this.hitDetectionCanvas_.getContext('2d');
      this.drawBack_(context,"#000",strokeWidth);
      context.fill();
    
      // Draw the image
      context = canvas.getContext('2d');
      this.drawBack_(context,strokeStyle,strokeWidth);
    
      // Draw a shadow
      if (this.shadow_)
      {	context.shadowColor = 'rgba(0,0,0,0.5)';
        context.shadowBlur = this.shadow_/2;
        context.shadowOffsetX = this.shadow_/2;
        context.shadowOffsetY = this.shadow_/2;
      }
      context.fill();
      context.shadowColor = 'transparent';
    
      var self = this;
      var img = this.img_ = new Image();
      if (this.crossOrigin_) img.crossOrigin = this.crossOrigin_;
      img.src = this.src_;
    
      // Draw image
      if (img.width) self.drawImage_(img);
      else img.onload = function()
      {	self.drawImage_(img);
        // Force change (?!)
        // self.setScale(1);
        if (self.onload_) self.onload_();
      };
    
      // Set anchor
      var a = this.getAnchor();
      a[0] = (canvas.width - this.shadow_)/2;
      a[1] = (canvas.height - this.shadow_)/2;
      if (this.sanchor_)
      {	a[1] = canvas.height - this.shadow_;
      }
    }
    
    /**
     * Draw an timage when loaded
     * @private
     */
    ol.style.Photo.prototype.drawImage_ = function(img)
    {	var canvas = this.getImage();
      // Remove the circle on the canvas
      var context = (canvas.getContext('2d'));
    
      var strokeWidth = 0;
      if (this.stroke_) strokeWidth = this.stroke_.getWidth();
      var size = 2*this.radius_;
    
      context.save();
      if (this.kind_=='circle')
      {	context.beginPath();
        context.arc(this.radius_+strokeWidth, this.radius_+strokeWidth, this.radius_, 0, 2 * Math.PI, false);
        context.clip();
      }
      var s, x, y, w, h, sx, sy, sw, sh;
      // Crop the image to a square vignette
      if (this.crop_)
      {	s = Math.min (img.width/size, img.height/size);
        sw = sh = s*size;
        sx = (img.width-sw)/2;
        sy = (img.height-sh)/2;
    
        x = y = 0;
        w = h = size+1;
      }
      // Fit the image to the size
      else
      {	s = Math.min (size/img.width, size/img.height);
        sx = sy = 0;
        sw = img.width;
        sh = img.height;
    
        w = s*sw;
        h = s*sh;
        x = (size-w)/2;
        y = (size-h)/2;
      }
      x += strokeWidth + this.sanchor_/2;
      y += strokeWidth;
    
      context.drawImage(img, sx, sy, sw, sh, x, y, w, h);
      context.restore();
    
      // Draw a circle to avoid aliasing on clip
      if (this.kind_=='circle' && strokeWidth)
      {	context.beginPath();
        context.strokeStyle = ol.color.asString(this.stroke_.getColor());
        context.lineWidth = strokeWidth/4;
        context.arc(this.radius_+strokeWidth, this.radius_+strokeWidth, this.radius_, 0, 2 * Math.PI, false);
        context.stroke();
      }
    }
    
    
    /**
     * @inheritDoc
     */
    ol.style.Photo.prototype.getChecksum = function()
    {
      var strokeChecksum = (this.stroke_!==null) ?
        this.stroke_.getChecksum() : '-';
      var fillChecksum = (this.fill_!==null) ?
        this.fill_.getChecksum() : '-';
    
      var recalculate = (this.checksums_===null) ||
        (strokeChecksum != this.checksums_[1] ||
          fillChecksum != this.checksums_[2] ||
          this.radius_ != this.checksums_[3]);
    
      if (recalculate) {
        var checksum = 'c' + strokeChecksum + fillChecksum
          + ((this.radius_ !== void 0) ? this.radius_.toString() : '-');
        this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
      }
    
      return this.checksums_[0];
    };
    

    做扩展的目的主要是为了以后少写几行代码,其实原生的方式也是可以实现该效果的。

    2、调用
    var vectorSource = new ol.source.Vector({
      url:"data/capital.geojson",
      format: new ol.format.GeoJSON()
    });
    var vector = new ol.layer.Vector({
      source: vectorSource,
      style: styleFunc
    });
    map.addLayer(vector);
    
    var id = 0;
    map.on("pointermove", function (e) {
      if(map.hasFeatureAtPixel(e.pixel)){
        map.getTargetElement().style.cursor = 'pointer';
      } else {
        map.getTargetElement().style.cursor = 'default';
      }
    });
    map.on("click", function (e) {
      if(map.hasFeatureAtPixel(e.pixel)){
        var features = map.getFeaturesAtPixel(e.pixel);
        var img = features[0].get('img');
        document.getElementById('photo').setAttribute('src', img);
        id = features[0].get('id');
        vector.setStyle(styleFunc);
      }
    });
    function styleFunc (feature){
      // var src = 'http://img18.3lian.com/d/file/201712/20/414dc24ceba7436ac6895d9e413ed2cc.png';
      var src = feature.get("img");
      return new ol.style.Style ({
        image: new ol.style.Photo({
          src: src,
          radius: 25,
          shadow: 2,
          kind: 'anchored', //default,square,circle,anchored,folio
          onload: function() { vector.changed(); },
          stroke: new ol.style.Stroke({
            width: 3,
            color: id ===feature.get('id') ? '#ffbcc8' : '#ffffff'
          })
        })
      })
    }	
    

    技术博客
    CSDN:http://blog.csdn.NET/gisshixisheng
    在线教程
    https://edu.csdn.net/course/detail/799
    https://edu.csdn.net/course/detail/7471
    联系方式

    类型 内容
    qq 1004740957
    公众号 lzugis15
    e-mail niujp08@qq.com
    webgis群 452117357
    Android群 337469080
    GIS数据可视化群 458292378

    LZUGIS

    展开全文
  • 使用Folium制作相册地图 folium是JavaScript上著名的交互式地图库leaflet.js为Python提供的接口,通过它,我们可以在Python端调用leaflet的相关功能。 leaflet通过内建的OpenStreetMap或自行获取的osm资源和地图原件...

    使用Folium制作相册地图

    folium是JavaScript上著名的交互式地图库leaflet.js为Python提供的接口,通过它,我们可以在Python端调用leaflet的相关功能。

    leaflet通过内建的OpenStreetMap或自行获取的osm资源和地图原件来进行地理信息内容的可视化,以及制作优美的可交互地图。其语法格式是通过不断添加图层元素来定义一个Map对象,最后以几种方式将Map对象展现出来。

    在定义了所有图层内容之后,folium会生成一个html文件,我们可以在浏览器中打开它,也可以基于jupyter notebook嵌入对应的交互地图。

    Folium官网

    这里展示一个只有一张图的小示例~
    包括图片在内的所有实现内容,包括实现相册地图,都在github上可以看:
    https://github.com/MewemeW/folium-album-map
    所有代码实现由Chensy Cao大神完成~
    在这里插入图片描述

    准备

    # # 科学计算
    # !pip install pandas
    # # 地理信息可视化
    # !pip install folium
    # !pip install branca
    # # 图像处理
    # !pip install pillow
    # !pip install opencv-python
    # # 交互式环境
    # !pip install IPython
    
    import ast
    import base64
    import os
    
    import branca
    import cv2
    import folium
    import pandas as pd
    import requests
    from folium.plugins import MarkerCluster
    from IPython.display import IFrame
    from PIL import Image
    from PIL.ExifTags import TAGS
    

    图像定位

    # 提取图片信息
    img=Image.open('./123/food.jpg')
    exifInfo = img._getexif()
    
    # 获取经纬度
    for tag, gpsValue in list(exifInfo.items()):
        decoded = TAGS.get(tag)
        if decoded == 'GPSInfo':
            gpsInfo = gpsValue
    
    Npos = sum([(item[0]/item[1])*(1.0/(60**i)) for i, item in enumerate(gpsInfo[2])])
    Epos = sum([(item[0]/item[1])*(1.0/(60**i)) for i, item in enumerate(gpsInfo[4])])
    location = [Npos, Epos]
    

    图片处理

    imagePathName = './mini123/food.jpg'
    encoded = base64.b64encode(open(imagePathName, 'rb').read())
    height, width = cv2.imread(imagePathName).shape[:2]
    

    建立地图

    tile = 
        'http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7'
    
    
    map = folium.Map(
        location=[30.24, 120.16],
        zoom_start=6,
        tiles=tile[2],
        attr='高德地图')
    
    # 标记
    icon_path = './icon/足迹.png'
    icon = folium.features.CustomIcon(icon_image=icon_path, icon_size=(30, 30))
    
    # 图像转为html
    HTML = """
                <center><img src='data:image/jpeg;base64,{}' style='width: {}px'></center>
                """
    
    # 定义图像的尺寸
    WIDTH = 650
    
    html = HTML.format(encoded.decode('UTF-8'), str(WIDTH))
    iframe = branca.element.IFrame(html=html, width=WIDTH+10, height=height-20)
    
    # 插件
    popup = folium.Popup(iframe, max_width=650)
    marker = folium.Marker(location=location, icon=icon,
                      popup=popup).add_to(map)
    
    # 展示
    mapName = '123.html'
    map.save(mapName)
    IFrame(src=mapName, width=980, height=600)
    

    具体实现的效果如下:
    在这里插入图片描述
    如果是相册(多图)地图呢~
    在这里插入图片描述
    分享完毕~

    展开全文
  • 本篇文章已授权微信公众号 guolin_blog (郭霖) 独家发布老规矩先上图, 高仿 ios 相册地图算法分析。下载.gif百度地图 SDK 新增点聚合功能。通过该功能,可通过缩小地图层级,将定义范围内的多个标注点,聚合显示...
  • 微图APP地理相册功能

    2019-05-10 11:17:37
    启动新版本微图APP,可以看到手机中拍摄的照片将会显示在地图上。 照片显示 放大地图后,会看到原本聚合在一起的照片将会逐渐分散开来,拍摄地点相近的会聚合在一起。 成绵高速沿线照片 富乐山景区照片 ....
  • geoPics.exe, 一个相册地图程序,可以利用照片拍摄时记录的经纬度信息,在地图上显示照片位置,还可以对照片定位信息进行修改。对于不带定位信息的jpg图片,也可以注入定位信息。所有功能仅需图片文件本身,无需任何...
  • 今天,我就接着来浅谈一下关于百度地图的内景,外景的展示功能。今天具体要实现的功能就是输入该地点的名称然后就展示该地点的内景图片,有内景就展示内景,没有则显示该地点的街景,该功能是参考百度地图官方的API来...
  • webview 调用相册权限和js地图权限

    千次阅读 2017-01-02 03:02:05
    这2个功能。我实现了,但是却冲突了, 就是有2个setWebChromeClient冲突。单独放2个功能是可以实现的,但一起的话,就只能实现一个。 然后求广大CSDN的朋友们求解。 部分代码如下:  super.onCreate...
  • 1.开开相册: private void selectImg() { Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); pickIntent.setDataAndType(MediaStore.Images.M...
  • 用过Instagram的用户都知道, 1.0版本的主打功能是利用创新的滤镜处理并分享照片;2.0版本则注重速度;而今日发布的Instagram 3.0 则可以把所有的照片贴到一幅世界地图上,在该服务中创建了一种全新的浏览照片的...
  • 今天具体要实现的功能就是输入该地点的名称然后就展示该地点的内景图片,有内景就展示内景,没有则显示该地点的街景,该功能是参考百度地图官方的API来实现。 实现街景展示的地点的搜索,需要涉及到几个方面的知识...
  • 苹果在中国的地图服务商是...在上述应用中,都用到了地图和定位功能 2:iOS定位依赖的框架 在iOS开发中,要想加入这2大功能,必须基于2个框架进行开发 Map Kit :用于地图展示(比较重量级) Core Location :...
  • 在pc端做人员调度功能,用到地图。看了一点高德地图API,由于手机端用的是百度地图,现在需要改用百度地图。下面把看的高德地图一点点成果记录下来: 1、在高德地图开放平台(http://lbs.amap.com/)注册帐号,我...
  • // 打开地图(北纬,东经) startActivity(intent); 打开拨号面板: Intent intent = new Intent(); intent.setAction(Intent.ACTION_DIAL); intent.setData(Uri.parse("tel:13807601111")); // 打开拨号面板 start...
  • 很多人都觉得iPhone手机的相册有点乱,对于有强迫症的宝宝来说更是蓝瘦,今天就教你几个小技巧,快速整理iPhone相册。按时间来整理要知道我们每次拍照片的时候,照片上面都会有时间信息。想按时间来整理的宝宝,看...
  • 《猛戳-查看我的博客地图-总有你意想不到的惊喜》 本文内容:开发过程中遇到的相关问题总结 H5端相机拍照原理 设置input标签的属性如下,capture为空会让用户选择本地文件或者拍照,onChange事件直接将图片文件...
  • </p><p>批次管理员</p><p>从收藏夹中筛选照片,进行选择并批量应用操作:更改作者,添加一些标签,关联到新相册,设置地理位置...</p><p>在地图上定位您的照片</p><p>Piwigo可以从嵌入式元数据中读取GPS纬度和经度。...
  • 拥有企业网站常用的模块功能(企业简介模块、新闻模块、产品模块、下载模块、图片模块、在线留言、在线订单、友情链接、网站地图等), 强大灵活的后台管理功能,可为企业打造出专业且具有营销力的标准网站。...
  • 1.开开相册:private void selectImg() {Intent pickIntent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT...
  • 微信JS-SDK 分享到朋友圈 分享给朋友 分享到QQ 拍照或从手机相册中选图 识别音频并返回识别结果 使用微信内置地图查看位置 一、JS部分 wx.ready(function () { // 1 判断当前版本是否支持指定 JS 接口,支持批量...
  • 奥维相册管理系统是奥维互动地图浏览器的辅助相册管理软件,是第一款支持奥维互动地图相册管理系统,支持在奥维地图中显示相片,使用奥维地图,可视化编辑照片的经纬度,有需要的赶快下载吧! 功能介绍 1、支持...
  • 微信JS接口 分享到朋友圈 分享给朋友 分享到QQ 拍照或从手机相册中选图 识别音频并返回识别结果 使用微信内置地图查看位置 来源:http://www.cnblogs.com/txw1958/p/weixin-js.html  概述 微信JS-SDK是微信...
  • 【android】百度地图初探

    千次阅读 2014-06-08 01:11:34
    今天要为相册管理app添加一个地图观察功能,主要是点击listview的图片后展开百度地图并在地图上标记zc
  • 地图与定位

    2015-08-07 22:02:30
    地图与定位标签(空格分隔): UI补充地图与定位在移动互联网时代我们可以去依靠手机上的地图导航区陌生的地方,也可用利用团购的app搜索最近的...上面的功能都都用到了地图和定位功能,在iOS开发中,要想加入这2大功能
  • 而且这款网络相册还有许多的功能,你可以将图像或电影的文件放入jalbum,软件会立马为你的图像创建一个缩略图,并将它们在一个html索引页中显示出来的,可以说是一款轻轻松就搞定的网络相册生...
  • 今天说说微信网页开发中一下JS的功能,分享一下,希望对各位有所帮助。前提:要有公众号,和通过微信认证,绑定域名,得到相应信息,appid,appsecret等。微信开发文档:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,918
精华内容 2,367
关键字:

地图相册功能