源码分析之Openlayers中样式篇Icon类

访问Openlayers网站(https://jinuss.github.io/Openlayers_map_pages/,网站是基于Vue3 + Openlayers,里面有大量的实践和案例。觉得还不错,可以在这里插入图片描述 给个小星星Star,鼓励一波 https://github.com/Jinuss/OpenlayersMap哦~

概述

在 Openlayers 中,Icon类用于创建图标,通常用于在地图上显示标记或图像元素。这个类可以通过设置图标的各种属性(如大小、图像源、锚点等)来控制图标的外观和位置。

Icon类继承于ImageStyle类,关于ImageStyle类,可以参考这篇文章源码分析之Openlayers中样式篇ImageStyle类

源码分析

Icon类源码实现

Icon类源码实现如下:

class Icon extends ImageStyle {
  constructor(options) {
    options = options || {};
    // 透明度
    const opacity = options.opacity !== undefined ? options.opacity : 1;
    // 旋转角度
    const rotation = options.rotation !== undefined ? options.rotation : 0;
    // 缩放大小
    const scale = options.scale !== undefined ? options.scale : 1;
    //是否跟随地图旋转
    const rotateWithView =
      options.rotateWithView !== undefined ? options.rotateWithView : false;

    super({
      opacity: opacity,
      rotation: rotation,
      scale: scale,
      displacement:
        options.displacement !== undefined ? options.displacement : [0, 0],
      rotateWithView: rotateWithView,
      declutterMode: options.declutterMode,
    });

    //锚点相关
    //图标相对锚点的位置
    this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];
    this.normalizedAnchor_ = null;
    //锚点的原点
    this.anchorOrigin_ =
      options.anchorOrigin !== undefined ? options.anchorOrigin : "top-left";
    // 锚点水平方向的单位
    this.anchorXUnits_ =
      options.anchorXUnits !== undefined ? options.anchorXUnits : "fraction";
    // 锚点垂直方向的位置
    this.anchorYUnits_ =
      options.anchorYUnits !== undefined ? options.anchorYUnits : "fraction";
    // 用于设置跨域请求的策略,默认为null
    this.crossOrigin_ =
      options.crossOrigin !== undefined ? options.crossOrigin : null;
    // 图标的图像
    const image = options.img !== undefined ? options.img : null;
    // 图像的唯一标识
    let cacheKey = options.src;

    assert(
      !(cacheKey !== undefined && image),
      "`image` and `src` cannot be provided at the same time"
    );

    if ((cacheKey === undefined || cacheKey.length === 0) && image) {
      cacheKey = /** @type {HTMLImageElement} */ (image).src || getUid(image);
    }
    assert(
      cacheKey !== undefined && cacheKey.length > 0,
      "A defined and non-empty `src` or `image` must be provided"
    );

    assert(
      !(
        (options.width !== undefined || options.height !== undefined) &&
        options.scale !== undefined
      ),
      "`width` or `height` cannot be provided together with `scale`"
    );

    let imageState; //图像状态
    if (options.src !== undefined) {
      imageState = ImageState.IDLE;
    } else if (image !== undefined) {
      if ("complete" in image) {
        if (image.complete) {
          imageState = image.src ? ImageState.LOADED : ImageState.IDLE;
        } else {
          imageState = ImageState.LOADING;
        }
      } else {
        imageState = ImageState.LOADED;
      }
    }

    // 图标的颜色
    this.color_ = options.color !== undefined ? asArray(options.color) : null;

    this.iconImage_ = getIconImage(
      image,
      cacheKey,
      this.crossOrigin_,
      imageState,
      this.color_
    );
    // 图标的偏移量
    this.offset_ = options.offset !== undefined ? options.offset : [0, 0];
    // 偏移量的原点
    this.offsetOrigin_ =
      options.offsetOrigin !== undefined ? options.offsetOrigin : "top-left";
    this.origin_ = null;
    // 图标的尺寸
    this.size_ = options.size !== undefined ? options.size : null;
    this.initialOptions_;

    // 如果用户同时提供了 width 或 height,则会根据图标的原始尺寸和目标尺寸进行缩放计算
    if (options.width !== undefined || options.height !== undefined) {
      let width, height;
      if (options.size) {
        [width, height] = options.size;
      } else {
        const image = this.getImage(1);
        if (image.width && image.height) {
          width = image.width;
          height = image.height;
        } else if (image instanceof HTMLImageElement) {
          this.initialOptions_ = options;
          const onload = () => {
            this.unlistenImageChange(onload);
            if (!this.initialOptions_) {
              return;
            }
            const imageSize = this.iconImage_.getSize();
            this.setScale(
              calculateScale(
                imageSize[0],
                imageSize[1],
                options.width,
                options.height
              )
            );
          };
          this.listenImageChange(onload);
          return;
        }
      }
      if (width !== undefined) {
        this.setScale(
          calculateScale(width, height, options.width, options.height)
        );
      }
    }
  }

  clone() {
    let scale, width, height;
    if (this.initialOptions_) {
      width = this.initialOptions_.width;
      height = this.initialOptions_.height;
    } else {
      scale = this.getScale();
      scale = Array.isArray(scale) ? scale.slice() : scale;
    }
    return new Icon({
      anchor: this.anchor_.slice(),
      anchorOrigin: this.anchorOrigin_,
      anchorXUnits: this.anchorXUnits_,
      anchorYUnits: this.anchorYUnits_,
      color:
        this.color_ && this.color_.slice
          ? this.color_.slice()
          : this.color_ || undefined,
      crossOrigin: this.crossOrigin_,
      offset: this.offset_.slice(),
      offsetOrigin: this.offsetOrigin_,
      opacity: this.getOpacity(),
      rotateWithView: this.getRotateWithView(),
      rotation: this.getRotation(),
      scale,
      width,
      height,
      size: this.size_ !== null ? this.size_.slice() : undefined,
      src: this.getSrc(),
      displacement: this.getDisplacement().slice(),
      declutterMode: this.getDeclutterMode(),
    });
  }

  getAnchor() {
    let anchor = this.normalizedAnchor_;
    if (!anchor) {
      anchor = this.anchor_;
      const size = this.getSize();
      if (
        this.anchorXUnits_ == "fraction" ||
        this.anchorYUnits_ == "fraction"
      ) {
        if (!size) {
          return null;
        }
        anchor = this.anchor_.slice();
        if (this.anchorXUnits_ == "fraction") {
          anchor[0] *= size[0];
        }
        if (this.anchorYUnits_ == "fraction") {
          anchor[1] *= size[1];
        }
      }

      if (this.anchorOrigin_ != "top-left") {
        if (!size) {
          return null;
        }
        if (anchor === this.anchor_) {
          anchor = this.anchor_.slice();
        }
        if (
          this.anchorOrigin_ == "top-right" ||
          this.anchorOrigin_ == "bottom-right"
        ) {
          anchor[0] = -anchor[0] + size[0];
        }
        if (
          this.anchorOrigin_ == "bottom-left" ||
          this.anchorOrigin_ == "bottom-right"
        ) {
          anchor[1] = -anchor[1] + size[1];
        }
      }
      this.normalizedAnchor_ = anchor;
    }
    const displacement = this.getDisplacement();
    const scale = this.getScaleArray();
    return [
      anchor[0] - displacement[0] / scale[0],
      anchor[1] + displacement[1] / scale[1],
    ];
  }
  setAnchor(anchor) {
    this.anchor_ = anchor;
    this.normalizedAnchor_ = null;
  }
  getColor() {
    return this.color_;
  }
  getImage(pixelRatio) {
    return this.iconImage_.getImage(pixelRatio);
  }
  getPixelRatio(pixelRatio) {
    return this.iconImage_.getPixelRatio(pixelRatio);
  }
  getImageSize() {
    return this.iconImage_.getSize();
  }
  getImageState() {
    return this.iconImage_.getImageState();
  }
  getHitDetectionImage() {
    return this.iconImage_.getHitDetectionImage();
  }
  getOrigin() {
    if (this.origin_) {
      return this.origin_;
    }
    let offset = this.offset_;

    if (this.offsetOrigin_ != "top-left") {
      const size = this.getSize();
      const iconImageSize = this.iconImage_.getSize();
      if (!size || !iconImageSize) {
        return null;
      }
      offset = offset.slice();
      if (
        this.offsetOrigin_ == "top-right" ||
        this.offsetOrigin_ == "bottom-right"
      ) {
        offset[0] = iconImageSize[0] - size[0] - offset[0];
      }
      if (
        this.offsetOrigin_ == "bottom-left" ||
        this.offsetOrigin_ == "bottom-right"
      ) {
        offset[1] = iconImageSize[1] - size[1] - offset[1];
      }
    }
    this.origin_ = offset;
    return this.origin_;
  }
  getSrc() {
    return this.iconImage_.getSrc();
  }
  getSize() {
    return !this.size_ ? this.iconImage_.getSize() : this.size_;
  }

  getWidth() {
    const scale = this.getScaleArray();
    if (this.size_) {
      return this.size_[0] * scale[0];
    }
    if (this.iconImage_.getImageState() == ImageState.LOADED) {
      return this.iconImage_.getSize()[0] * scale[0];
    }
    return undefined;
  }

  getHeight() {
    const scale = this.getScaleArray();
    if (this.size_) {
      return this.size_[1] * scale[1];
    }
    if (this.iconImage_.getImageState() == ImageState.LOADED) {
      return this.iconImage_.getSize()[1] * scale[1];
    }
    return undefined;
  }

  setScale(scale) {
    delete this.initialOptions_;
    super.setScale(scale);
  }

  listenImageChange(listener) {
    this.iconImage_.addEventListener(EventType.CHANGE, listener);
  }

  load() {
    this.iconImage_.load();
  }

  unlistenImageChange(listener) {
    this.iconImage_.removeEventListener(EventType.CHANGE, listener);
  }
  ready() {
    return this.iconImage_.ready();
  }
}

Icon类的构造函数

Icon类构造函数的核心作用是初始化 Icon 实例,并根据传入的 options 配置来设置图标的各种属性,如透明度、旋转角度、缩放比例、图像来源等。它还会处理图标图像的加载、偏移、锚点等相关设置。

Icon类的主要方法

  • clone()方法:复制一个Icon对象,内部就是实例化一个Icon类,并返回实例对象。

  • getAnchor()方法:计算并返回图标的锚点位置,getAnchor方法首先会检查normalizedAnchor_属性;若已经计算郭锚点位置,则直接返回该值;否则计算基于尺寸的锚点;计算的依据就是锚点的X或者Y单位以及锚点的位置。计算后会保存锚点,以便下次直接使用,避免重复计算;最后会计算位移以及缩放,返回最终的锚点。

  • setAnchor(anchor)方法:接受一个数组anchor,并将它赋值给this.anchor,然后将this.normalizedAnchor置空null

  • getColor()方法:获取this.color_属性

  • getImage(pixelRatio)方法:返回与图标相关的图像对象,pixelRatio参数是像素比率

  • getPixelRatio(pixelRatio)方法:返回图标图像的像素比率

  • getImageSize()方法:返回图标图像的尺寸(宽度和高度),形如[宽度,高度]

  • getImageState()方法:这个方法返回图像的当前加载状态。(ImageState.IDLE:图像处于空闲状态,尚未加载、ImageState.LOADING:图像正在加载中、ImageState.LOADED:图像加载完成、ImageState.ERROR:图像加载失败)

  • getHitDetectionImage()方法:用于点击检测的图像

  • getOrigin()方法:根据设置的偏移量、原点位置和图标大小来计算并返回图标或标注的“原点”位置。

  • getSrc()方法:返回图标图片的源 URL

  • getSize()方法:获取图标的尺寸

  • getWidth()方法:获取图标的宽度

  • getHeight()方法:获取图标的高度

  • setScale(scale)方法: 设置图标的缩放比例

  • listenImageChange(listener)方法:监听图标图像的变化事件

  • load()方法:加载图标图像

  • unlistenImageChange(listener)方法:移除监听图标图像变化的事件处理函数

  • ready()方法:检查图标图像是否已准备好

总结

Icon类主要就是管理图标包括其位置、尺寸、颜色、加载状态和缩放等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinuss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值