Angular17+leaflet集成天地图组件

Angular17+leaflet集成天地图组件

例图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

需要的包

"@asymmetrik/ngx-leaflet": "^17.0.0","@types/leaflet": "^1.9.12","leaflet": "^1.9.4",

去天地图网站获取一个token

https://www.tianditu.gov.cn/

创建Angular组件component

名称:site-pick-tianditu

html
<div><input #searchInput nz-input placeholder="请输入地址" /><!-- 搜索结果的下拉框 --><ul *ngIf="searchResults.length > 0" class="search-dropdown"><li *ngFor="let result of searchResults" (click)="selectLocation(result)">{{ result.address }}{{ result.name }}</li></ul><div #mapContainer id="mapContainer"></div>
</div>
less 样式
#mapContainer {width: 100%;height: 500px;  /* 确保地图显示正确 */
}.search-dropdown {position: absolute;background-color: white;border: 1px solid #ddd;width: 100%;max-height: 200px;overflow-y: auto;list-style: none;padding: 0;margin: 0;z-index: 1000;li {padding: 8px 12px;cursor: pointer;&:hover {background-color: #f0f0f0;}}
}
ts
import {AfterViewInit, ChangeDetectorRef,Component,ElementRef, EventEmitter, HostListener, Input, OnChanges,OnInit, Output, SimpleChanges,ViewChild,ViewEncapsulation
} from '@angular/core';
import * as L from 'leaflet';
import {NzInputDirective, NzInputGroupComponent} from "ng-zorro-antd/input";
import {LeafletModule} from "@asymmetrik/ngx-leaflet";
import {fromEvent, Subject} from "rxjs";
import {debounceTime,map} from "rxjs/operators";
import {NgForOf, NgIf} from "@angular/common";@Component({selector: 'app-site-pick-tianditu',standalone: true,imports: [NzInputDirective,LeafletModule,NzInputGroupComponent,NgForOf,NgIf],templateUrl: './site-pick-tianditu.component.html',styleUrls: ['./site-pick-tianditu.component.less'],encapsulation: ViewEncapsulation.None  // 禁用样式封装
})
export class SitePickTiandituComponent  implements OnInit, AfterViewInit,OnChanges  {@ViewChild('mapContainer', {static: false}) mapContainer!: ElementRef;@ViewChild('searchInput', {static: false}) searchInput!: ElementRef;@Output() inputChange = new EventEmitter<{ lonlat: any, siteName: any, adCode: any }>();@Input() lonlat!: string;@Input() locationName!: string;@Input() boundary!: string;constructor(private cdr: ChangeDetectorRef) {}map!: L.Map;currentMarker: any;drawMapEvent = new Subject();mapLoaded = false;public mapLoadSubject = new Subject<void>();key = "XXXXXXXXXXXXXXXXXXXXXXXX"; // 天地图API KeydefaultCenter = [116.397755, 39.903179]; // 默认中心,北京selectedLocation: L.LatLng | null = null;searchResults: any[] = []; // 保存搜索结果的数组// 天地图瓦片URL   影像底图tiandituImageLayerUrl = 'https://t{s}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + this.key;//影像底图- 影像注记tiandituImageLayerUrlMark = 'https://t{s}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + this.key;//影像底图- 矢量底图tiandituVecLayerUrl = 'https://t{s}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + this.key;//影像底图- 矢量注记tiandituVeceLayerUrlMark = 'https://t{s}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=' + this.key;@HostListener('window:resize', [])onWindowResize() {this.map.invalidateSize();  // 在窗口大小调整时强制刷新地图}ngOnInit(): void {this.drawMapEvent.subscribe(() => {this.currentPosition().subscribe(center => {this.addSearchPlugin();});});}ngOnChanges(changes: SimpleChanges): void {if (changes['lonlat'] && this.lonlat) {const [lng, lat] = this.lonlat.split(',').map(Number);if (!this.locationName) {this.reverseGeocode(lat, lng);} else {this.searchInput.nativeElement.value = this.locationName;this.map.setView([lat, lng], 15);this.addMarker(lat, lng);}}}ngAfterViewInit(): void {this.initializeMap();this.addSearchPlugin(); // 检查输入框事件监听// 在地图初始化后立即刷新瓦片setTimeout(() => {this.map.invalidateSize();}, 1);}initializeMap(): void {// 初始化地图,设置默认中心和缩放级别this.map = L.map(this.mapContainer.nativeElement, {center: [39.9042, 116.4074], // 北京市中心坐标zoom: 15,maxZoom: 18,                    // 天地图最大缩放级别minZoom: 1,                      // 最小缩放级别,防止缩放太小或太大});L.control.scale({imperial: false}).addTo(this.map);// 设置自定义的 icon 路径L.Icon.Default.mergeOptions({iconRetinaUrl: 'assets/images/marker-icon-2x.png',iconUrl: 'assets/images/marker-icon.png',shadowUrl: 'assets/images/marker-shadow.png'});// 创建图层 - 影像底图const imageLayer = L.tileLayer(this.tiandituImageLayerUrl, {subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],maxZoom: 18,minZoom: 3});// 创建图层 - 影像注记const imageLayerMark = L.tileLayer(this.tiandituImageLayerUrlMark, {subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],maxZoom: 18,minZoom: 3});// 创建图层 - 矢量底图const vecLayer = L.tileLayer(this.tiandituVecLayerUrl, {subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],maxZoom: 18,minZoom: 3});// 创建图层 - 矢量注记const vecLayerMark = L.tileLayer(this.tiandituVeceLayerUrlMark, {subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],maxZoom: 18,minZoom: 3});// 默认添加矢量底图vecLayer.addTo(this.map);vecLayerMark.addTo(this.map);// 图层控制器const baseLayers = {"影像底图": imageLayer,"矢量底图": vecLayer,// 如果有其他基础图层也可以添加到这里,例如矢量地图};const overlayLayers = {"影像注记": imageLayerMark,"矢量注记": vecLayerMark,};L.control.layers(baseLayers, overlayLayers).addTo(this.map);this.map.on('click', (e: L.LeafletMouseEvent) => {this.onMapClick(e);});this.drawMapEvent.next(null);}// 使用 RxJS 监听搜索框输入,防抖500msaddSearchPlugin(): void {fromEvent(this.searchInput.nativeElement, 'input').pipe(map((event: any) => {return event.target.value;}),debounceTime(500)  // 防抖,500ms延迟触发搜索).subscribe((keyword: string) => {if (keyword.length > 2) {this.searchLocation(keyword);  // 当输入字符大于2时开始搜索}});}// 调用天地图搜索APIsearchLocation(keyword: string): void {const searchUrl = `https://api.tianditu.gov.cn/v2/search?postStr={"keyWord":"${keyword}","level":"10","mapBound":"-180,-90,180,90","queryType":"7","count":"10","start":"0"}&tk=${this.key}`;fetch(searchUrl).then(response => response.json()).then(data => {// 检查 API 响应是否包含 poisif (data.count > 0 && Array.isArray(data.pois)) {this.searchResults = data.pois.map((poi: any) => ({name: poi.name,lonlat: poi.lonlat,adminname: poi.adminname,address: poi.address}));} else {// 如果返回数据结构不符合预期,或者没有搜索到结果console.warn('未找到匹配的地点');this.searchResults = [];}this.cdr.detectChanges(); // 强制变更检测以更新 UI}).catch(error => {console.error('搜索地址时出错:', error);this.searchResults = [];});}// 选择下拉框中的地点selectLocation(result: any): void {const [lng, lat] = result.lonlat.split(',').map(Number);// 将 name 和 address 组合并设置到输入框中this.searchInput.nativeElement.value = `${result.address}${result.name}`;this.resolveLocation(lat, lng, this.searchInput.nativeElement.value, result.adminname); // 定位地图this.searchResults = []; // 清空搜索结果,关闭下拉框}// 定位地图到指定位置resolveLocation(lat: number, lng: number, siteName: string, adCode: string): void {if (this.currentMarker) {this.map.removeLayer(this.currentMarker);}this.currentMarker = L.marker([lat, lng]).addTo(this.map);this.map.setView([lat, lng], 15);this.inputChange.emit({ lonlat: `${lng},${lat}`, siteName, adCode }); // 传递地点信息}onMapClick(e: L.LeafletMouseEvent): void {const { lat, lng } = e.latlng;const reverseGeocodeUrl = `https://api.tianditu.gov.cn/geocoder?postStr={'lon':${lng},'lat':${lat},'ver':1}&type=geocode&tk=${this.key}`;// 调用天地图逆地址编码接口fetch(reverseGeocodeUrl).then(response => response.json()).then(data => {if (data.status === '0') {const formattedAddress = data.result.formatted_address;// 将获取到的地址回显到输入框中this.searchInput.nativeElement.value = formattedAddress;// 将经纬度和地址传递出去this.inputChange.emit({lonlat: `${lng},${lat}`,siteName: formattedAddress,adCode: '' // 如果需要,可以从返回的数据中解析 adCode});// 在地图上标记选择的位置if (this.currentMarker) {this.map.removeLayer(this.currentMarker);}this.currentMarker = L.marker([lat, lng]).addTo(this.map);this.map.setView([lat, lng], 15);} else {console.error('逆地址编码失败:', data.msg);}}).catch(error => {console.error('调用逆地址编码接口时出错:', error);});}reverseGeocode(lat: number, lng: number): void {const reverseGeocodeUrl = `https://api.tianditu.gov.cn/geocoder?postStr={'lon':${lng},'lat':${lat},'ver':1}&type=geocode&tk=${this.key}`;fetch(reverseGeocodeUrl).then(response => response.json()).then(data => {if (data.status === '0') {const formattedAddress = data.result.formatted_address;// 将逆地址编码获取的地址回显到输入框this.searchInput.nativeElement.value = formattedAddress;// 将经纬度和地址通过事件传递出去this.inputChange.emit({lonlat: `${lng},${lat}`,siteName: formattedAddress,adCode: ''});// 定位并添加地图标记this.map.setView([lat, lng], 15);this.addMarker(lat, lng);} else {console.error('逆地址编码失败:', data.msg);}}).catch(error => console.error('调用逆地址编码接口时出错:', error));}addMarker(lat: number, lng: number): void {if (this.currentMarker) {this.map.removeLayer(this.currentMarker);}this.currentMarker = L.marker([lat, lng]).addTo(this.map);}currentPosition(): Subject<any> {return new Subject();}
}

用法

在这里插入图片描述
: [lonlat]=“form.get(‘lonlat’).value” 这里是将form中的经纬度值传入进组件,组件会自动定位到具体地点
(inputChange)=“inputChange($event)” 这里是获取组件传出来的改变值;

  /*** 地图input框选中返回lonlat+name* @param $event*/inputChange($event: any) {this.form.get('lonlat').setValue($event.lonlat);this.form.get('address').setValue($event.siteName);}

这里进行将传出来的经纬度和地点名称进行一个赋值

注意:我的经纬度lonlat是通过逗号“,”分隔的字符串

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1539890.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

8、创建一般资产负债科目

定义解释 在前台创建资产负债类科目。定义资产负债类会计科目。 在 SAP 中,会计科目的定义分为两层&#xff1a;科目表层和公司代码层。 比如&#xff1a;一个集团企业可以定义一套会计科目表&#xff0c;但是下面的每个公司代码&#xff0c;从中选择哪些科目&#xff0c;这…

透明LED模块的应用场景

随着科技的不断进步和市场需求的增长&#xff0c;透明LED显示屏的应用越来越广泛&#xff0c;成为了众多领域中的创新亮点。透明LED模块凭借其高通透率、轻薄设计以及与环境的完美融合&#xff0c;正逐步替代传统显示方式&#xff0c;为现代化的展示和广告提供了全新的解决方案…

PicoQuant公司:探索铜铟镓硒(CIGS)太阳能电池技术,引领绿色能源革新

铜铟镓硒&#xff08;‌CIGS&#xff09;‌薄膜太阳能电池具有生产成本低、污染小、不衰退、弱光性能好等显著特点&#xff0c;光电转换效率居各种薄膜太阳电池之首&#xff0c;接近于晶体硅太阳电池&#xff0c;而成本只是它的三分之一&#xff0c;被称为下一代非常有前途的新…

window批处理脚本:将本地的三个文件通过SCP传输到Linux设备上

文件名send_file.bat&#xff1a; echo off setlocal:: 提示用户输入远程IP地址 set /p remoteIpAddressplease input IP::: 定义本地文件名 set "localFile1111" set "localFile2222" set "localFile3333":: 获取本地文件的完整路径 set "…

项目与产品的生命周期

在项目管理领域有两个关键概念&#xff0c;即项目生命周期和产品生命周期。它们是理解项目管理过程中的关键要素。 项目生命周期是一个项目从概念到完成所经过的所有阶段。所有项目都可分成若干阶段&#xff0c;且所有项目无论大小&#xff0c;都有一个类似的生命周期结构。阶…

学习大数据DAY59 全量抽取和增量抽取实战

目录 需求流程&#xff1a; 需求分析与规范 作业 作业2 需求流程&#xff1a; 全量抽取 增量抽取 - DataX Kettle Sqoop ... 场景: 业务部门同事或者甲方的工作人员给我们的部门经理和你提出了新的需 求 流程: 联系 > 开会讨论 > 确认需求 > 落地 需求文档( 具体…

day-56 整数转罗马数字

思路 因为题目已经告知num<3999&#xff0c;所以只需依次对1000,900,500,400,100,90,50,40,10,9,5,4,1这些数进行除法&#xff0c;判断商是否大于等于1&#xff0c;如果大于等于1则将对应的罗马数字拼接到字符串中即可 解题过程 为了方便拼接&#xff0c;可以使用哈希键值对…

关于用matplotlib.pyplot加载图片颜色不对的解决方法

1.原理&#xff1a;用opencv加载的图片是BGR存储的。而用matplotlib.pyplot 需要RGB的格式,故在加载之前使用下面的语句改成RGB格式。 img2 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 或使用 # img2 img[:, :, ::-1]#将图像img的颜色通道进行反转。 ::-1 表示在最后一个维度…

【吊打面试官系列-MySQL面试题】LIKE 声明中的%和_是什么意思?

大家好&#xff0c;我是锋哥。今天分享关于【LIKE 声明中的&#xff05;和_是什么意思&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; LIKE 声明中的&#xff05;和_是什么意思&#xff1f; &#xff05;对应于 0 个或更多字符&#xff0c;_只是 LIKE 语句中的…

图纸管理系统到底是什么?适合哪些企业去部署?

图纸管理系统作为现代企业数字化转型的基石&#xff0c;正逐步成为各行各业不可或缺的重要工具。它不仅革新了传统图纸的存储与管理方式&#xff0c;更通过集成化、智能化的功能&#xff0c;为企业带来了前所未有的工作效率与数据安全保障。无论是建筑设计、制造业还是航空航天…

Prometheus架构详解

1 Prometheus简介 Prometheus 是一个开源的系统监控报警工具套件&#xff0c;它最初由SoundCloud开发&#xff0c;并于2016年成为CNCF&#xff08;云原生计算基金会&#xff09;托管的第二个项目&#xff08;第一个是kubernetes&#xff09;。Prometheus 以其简单高效的方式收…

华为云服务综合实验

一、实验需求 本次实验内容基于华为云平台模拟企业web集群的构建&#xff0c;其中涉及的知识点包括Linux系统知识、nginx服务的安装及配置应用、云数据库 RDS(Relational Database Service,简称RDS)、虚拟私有云vpc、安全组、SFS弹性文件服务器以及负载均衡等。要求学生通过本…

基于SSM的“大学生兼职平台”的设计与实现(源码+数据库+文档+开题报告)

基于SSM的“大学生兼职平台”的设计与实现&#xff08;源码数据库文档开题报告) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统结构图 兼职平台首页 企业信息 用户注册界面 在线留…

光伏场地建设规划 - 华为OD统一考试(E卷)

2024华为OD机试&#xff08;C卷D卷E卷&#xff09;最新题库【超值优惠】Java/Python/C合集 题目描述 祖国西北部有一片大片荒地&#xff0c;其中零星的分布着一些湖泊&#xff0c;保护区&#xff0c;矿区;整体上常年光照良好&#xff0c;但是也有一些地区光照不太好。某电力公…

Android中SharedPreferences 的基本使用

1.SharedPreferences简介 SharedPreferences 是 Android 平台为应用开发者提供的一个轻量级的存储辅助类&#xff0c;用来保存应用的一些常用配置&#xff0c;它提供了 putString()、putString(Set<String>)、putInt()、putLong()、putFloat()、putBoolean() 六种数据类…

uniapp+renderJS+google map开发安卓版APP非小程序

背景需求 需要在uniapp中接入google地图,研究了一番,都没有找到合适的,现在说一下教程。 效果图 前期工作 这两点缺一不可,否则你啥也看不到。 1、电脑安装L-O-U梯 用于访问G-OO-G-LE的API或者创建google map key。 2、手机安装L-O-U梯 用于显示google地图。我就是手…

二叉树(下)

目录 判断树是否相同 判断树是不是另一棵树的子树 二叉树翻转 判断平衡二叉树 二叉树层序遍历 这篇主要提供一些关于二叉树例题的讲解&#xff0c;如果对二叉树及其基本操作有疑问的可以转至&#xff1a; 二叉树&#xff08;上&#xff09;-CSDN博客二叉树&#xff08;中&…

苹果手机删除的视频怎么恢复?记住这3个秘诀

我们的手机相册中保存着大量珍贵的视频&#xff0c;这些视频记录着日常生活的美好时刻。但是&#xff0c;如果不小心删除了怎么办呢&#xff1f;别担心&#xff0c;今天我们将揭示3个解决手机删除的视频怎么恢复问题的秘诀&#xff0c;下面&#xff0c;跟着小编的步伐&#xff…

Google Gemini 与 OpenAI 激烈竞赛:语音 AI 与未来智能体的技术演进

引言 最近&#xff0c;人工智能领域频频传出令人震惊的消息&#xff0c;尤其是在Google Gemini 和OpenAI两大巨头的竞争中&#xff0c;语音AI和未来智能体的技术发展更是成为焦点。视频中的相关报道提到&#xff0c;Google已经将其最新的语音助手Google Gemini Alive推广给更多…

兔子检测系统源码分享

兔子检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …