如何使用 Leaflet 构建 Angular 地图,第 2 部分:标记服务

介绍

Leaflet 支持标记这些是放置在地图上的可以包含信息的指示器。这提供了一种在地图上突出显示地标和目的地的方法。

注意:这是关于使用 Angular 和 Leaflet 的 4 部分系列的第 2 部分。

在本教程中,您将学习如何使用管理标记逻辑的服务向地图添加标记。

先决条件

要完成本教程,您需要:

第 1 步 – 下载 GeoJSON 数据

本教程将绘制美国各州首府的GeoJSON数据。它还包括一些关于州名、首都名和人口的额外元数据。

注意: usa-capitals.geojson文件在随附的项目 repo 中可用

data目录下新建一个子目录assets

  • mkdir src/assets/data

然后,将usa-capitals.geojson文件保存在此目录中。

第 2 步 – 创建标记服务

此时,您应该在 Angular 应用程序中有一个 Leaflet 的工作实现。

使用终端窗口导航到项目目录。然后,运行以下命令以生成新服务:

  • npx @angular/cli generate service marker --skip-tests

这将创建一个新文件:marker.service.ts.

接下来,您将在您的app.module.ts. 您还将从您的assets文件夹加载数据,因此您需要包含HttpClientModule.

app.module.ts在代码编辑器中打开并进行以下更改:

src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { HttpClientModule } from '@angular/common/http';
import { MarkerService } from './marker.service';

import { AppComponent } from './app.component';
import { MapComponent } from './map/map.component';

@NgModule({
  declarations: [
    AppComponent,
    MapComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    MarkerService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

您的应用程序现在支持您的新MarkerService.

第 3 步 – 加载和绘制标记

接下来,marker.service.ts在代码编辑器中打开新创建的并添加HttpClient到构造函数中:

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) { }
}

创建一个将加载 GeoJSON 数据并创建标记的新函数。此函数将接受 Leaflet 地图作为参数。

修改marker.service.ts导入 Leaflet 并声明一个makeCapitalMarkers函数:

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) { }

  makeCapitalMarkers(map: L.map): void { }
}

使用HttpClient,获取数据并subscribe得到结果。

获得数据后,您将遍历每个要素,构建一个标记,并将其添加到地图中。

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) {
  }

  makeCapitalMarkers(map: L.map): void {
    this.http.get(this.capitals).subscribe((res: any) => {
      for (const c of res.features) {
        const lon = c.geometry.coordinates[0];
        const lat = c.geometry.coordinates[1];
        const marker = L.marker([lat, lon]);

        marker.addTo(map);
      }
    });
  }
}

此代码处理加载和向地图添加标记的逻辑。

现在,您必须从MapComponent以下位置调用此方法

src/app/map/map.component.ts
import { Component, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { MarkerService } from '../marker.service';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
  private map;

  private initMap(): void {
    this.map = L.map('map', {
      center: [ 39.8282, -98.5795 ],
      zoom: 3
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      minZoom: 3,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

    tiles.addTo(this.map);
  }

  constructor(private markerService: MarkerService) { }

  ngAfterViewInit(): void {
    this.initMap();
    this.markerService.makeCapitalMarkers(this.map);
  }
}

如果此时您要运行您的应用程序,您会在控制台中遇到两个错误:

Output
marker-icon-2x.png:1 GET http://localhost:4200/marker-icon-2x.png 404 (Not Found) marker-shadow.png:1 GET http://localhost:4200/marker-shadow.png 404 (Not Found)

您需要将 Leaflet 的资产导入到您的项目中以引用marker-icon-2x.pngmarker-shadow.png图像文件。

打开angular.json文件并添加 Leafletimages目录:

angular.json
{
  // ...
  "projects": {
    "angular-leaflet-example": {
      // ...
      "architect": {
        "build": {
          // ...
          "options": {
            // ...
            "assets": [
              "src/favicon.ico",
              "src/assets",
              {
                "glob": "**/*",
                "input": "node_modules/leaflet/dist/images/",
                "output": "./assets"
              }
            ],
            // ..
          },
          // ...
        },
        // ...
      }
    }},
  "defaultProject": "angular-leaflet-example"
}

此代码将在本地复制 Leaflet 的标记图像。

然后,重新访问map.component.ts并定义图标:

src/app/map/map.component.ts
import { Component, AfterViewInit } from '@angular/core';
import * as L from 'leaflet';
import { MarkerService } from '../marker.service';

const iconRetinaUrl = 'assets/marker-icon-2x.png';
const iconUrl = 'assets/marker-icon.png';
const shadowUrl = 'assets/marker-shadow.png';
const iconDefault = L.icon({
  iconRetinaUrl,
  iconUrl,
  shadowUrl,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowSize: [41, 41]
});
L.Marker.prototype.options.icon = iconDefault;

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.css']
})
export class MapComponent implements AfterViewInit {
  private map;

  constructor(private markerService: MarkerService) { }

  private initMap(): void {
    this.map = L.map('map', {
      center: [ 39.8282, -98.5795 ],
      zoom: 3
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 18,
      minZoom: 3,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

    tiles.addTo(this.map);
  }

  ngAfterViewInit(): void {
    this.initMap();
    this.markerService.makeCapitalMarkers(this.map);
  }
}

保存您的更改。然后,停止您的应用程序并重新启动它。在 Web 浏览器 ( localhost:4200) 中打开应用程序并观察州首府的标记:

美国地图的屏幕截图,带有指示州首府位置的标记图钉。

此时,您有一个支持默认标记的地图。

第 4 步 – 显示圆形标记

在下一步中,您会将标记从图标更改为圆圈。然后缩放圆圈的大小以反映州议会大厦的人口。

打开MarkerService并创建一个makeCapitalCircleMarkers()函数。它将与makrCapitalMarkers()功能非常相似在 Leaflet 的marker方法中,您将使用该circleMarker方法:

src/app/marker.service.ts
makeCapitalCircleMarkers(map: L.map): void {
  this.http.get(this.capitals).subscribe((res: any) => {
    for (const c of res.features) {
      const lon = c.geometry.coordinates[0];
      const lat = c.geometry.coordinates[1];
      const circle = L.circleMarker([lat, lon]);

      circle.addTo(map);
    }
  });
}

然后,调用这个函数MapComponent

src/app/map/map.component.ts
ngAfterViewInit(): void {
  this.initMap();
  // this.markerService.makeCapitalMarkers(this.map);
  this.markerService.makeCapitalCircleMarkers(this.map);
}

保存这些更改并在 Web 浏览器 ( localhost:4200) 中打开应用程序

美国地图的屏幕截图,其中圆圈表示州首府的位置。

图标现在已替换为圆圈。

circleMarker接受第三个可选参数。这个对象可以包含一个radius属性。在您的 中MarkerService,修改makeCapitalCircleMarkers函数以使用半径20

const circle = L.circleMarker([lat, lon], { radius: 20 }).addTo(map);

此代码将所有半径的大小设置为相同的值 ( 20)。

接下来,您将更改半径以反映州首府的人口:

static scaledRadius(val: number, maxVal: number): number {
  return 20 * (val / maxVal);
}

此函数接受一个值(人口)、一个最大值(最大人口),并返回范围 [0 – 20] 内的半径。

您将使用传播运算符并map找到人口最多的首都:

const maxPop = Math.max(...res.features.map(x => x.properties.population), 0);

从 GeoJSON 数据来看,最大的人口将是:“亚利桑那州凤凰城”(1626078)。

最后,您将使用ScaledRadius作为半径函数将它们组合在一起

MarkerService在代码编辑器中打开并进行以下更改:

src/app/marker.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class MarkerService {
  capitals: string = '/assets/data/usa-capitals.geojson';

  constructor(private http: HttpClient) { }

  static scaledRadius(val: number, maxVal: number): number {
    return 20 * (val / maxVal);
  }

  makeCapitalMarkers(map: L.map): void {
    this.http.get(this.capitals).subscribe((res: any) => {
      for (const c of res.features) {
        const lon = c.geometry.coordinates[0];
        const lat = c.geometry.coordinates[1];
        const marker = L.marker([lat, lon]);

        marker.addTo(map);
      }
    });
  }

  makeCapitalCircleMarkers(map: L.map): void {
    this.http.get(this.capitals).subscribe((res: any) => {

      const maxPop = Math.max(...res.features.map(x => x.properties.population), 0);

      for (const c of res.features) {
        const lon = c.geometry.coordinates[0];
        const lat = c.geometry.coordinates[1];
        const circle = L.circleMarker([lat, lon], {
          radius: MarkerService.scaledRadius(c.properties.population, maxPop)
        });

        circle.addTo(map);
      }
    });
  }
}

保存您的更改。然后,停止您的应用程序并重新启动它。在 Web 浏览器 ( localhost:4200) 中打开应用程序并观察州首府的新比例圆圈标记:

美国地图的屏幕截图,圆圈按比例缩放以指示人口规模。

您现在有一个支持标记的地图。

结论

在这篇文章中,您创建了一个标记服务来加载数据并构建标记。您学习了如何创建两种类型的标记:L.markerL.circleMarker最后,您学习了如何通过传递半径函数来定义每个圆形标记的大小。

继续阅读本系列的第 3 部分关于使用 Angular 和 Leaflet

觉得文章有用?

点个广告表达一下你的爱意吧 !😁