8.2. 基于属性值的图层符号化

Choropleth 地图、比例符号地图和动态特性过滤都为典型的“地图上的点”mashup提供了替代功能。你怎么能用 Leaflet 做到这些?在本课的这一节中,我们将讨论如何基于数据中的属性创建 Leaflet 符号。然后,您可以将这些符号应用于向量以获得各种类型的地图,例如基于类分隔的choropleth 地图。

首先,需要明确的是,使用 Leaflet 代码在客户机上标记层并不是在网络上实现choropleth地图、比例符号等的唯一方法。在本课程的前面,您已经熟悉了由SLD样式定义的动态绘制WMS地图和由CartoCSS定义的瓦片地图。在这些方法中,您在服务器上而不是在客户机上进行制图。服务器端地图最适合高级制图效果。当有成百上千的功能在运行时,它们也可能会产生更好的性能。

那么,既然有那么多其他制图工具可以使用,为什么我们要讨论用 Leaflet 样式制作我们的专业地图呢?原因是在客户机上定义符号打开了灵活性和交互性的大门。

  • 灵活性是因为数据与样式分离,因此,您不必设置多个Web服务,以便在应用程序中获得多种类型的样式。每个应用程序可以在其客户端代码中定义不同的样式规则,同时使用相同的源数据。

  • 交互性来自通过调用客户端代码功能即时进行重新分类和符号化的能力。例如,您可以允许用户从等间隔分类切换为分位数分类,或调整比例符号的最大大小,而无须返回服务器或设置其他Web服务。

8.2.1. 阅读 Leaflet 中的属性

为了基于某些属性值创建样式规则,首先需要读取属性。您如何在Leaflet中做到这一点?

让我们考虑一下我从布宜诺斯艾利斯开放数据门户网站(https://data.buenosaires.gob.ar/dataset>_)下载的数据集,该数据集显示了地铁(“ subte”)线路。每条地铁线由许多折线段组成。每个折线线段都有一个属性,指明其所属的地铁线(例如“ LINEA B”)。我已经从中创建了一个GeoJSON文件,并希望使用每条地铁线的独特颜色在网络地图中显示该文件,如下所示:

../../_images/subway_map.png

图8.1

首先,让我们以颜色值直接编码到属性表中的简单情况为例。请注意下面的颜色字段,其中包含每个线段的颜色十六进制值:

../../_images/subway_attributes.png

图8.2

如果您很幸运可以进行此设置,并且颜色适合您,那么在定义图层样式时,可以将它们直接应用于color属性。在Leaflet中,可以使用语法feature.properties。<PROPERTY>来获取任何要素属性的值:

// Set up styles for subway lines
function subteStyle(feature) {
  return {
    "color": feature.properties.COLOR,
    "weight": 5
  };
}
// Create layer and add it to the map
var subteLayer = L.geoJSON(subteData, {
  style: subteStyle
});

上面的代码根据来自GeoJSON文件的变量subteData创建矢量层。要为图层设置样式,subteStyle函数将从COLOR字段读取十六进制值,并将其插入图层的color属性中。请注意如何使用语法feature.properties.COLOR来读取颜色值。

8.2.2. 唯一命名属性的符号

尽管上述技术很方便,但并不总是实用的。如果文件中提供的颜色不适合您的地图怎么办?幸运的是,在我们的场景中,您可以根据地铁线路的名称在JavaScript(即客户端)代码中提供颜色值来解决这个问题。

让我们看看如何使用 Leaflet 在客户端应用一些样式规则。请检查前面查看的代码的以下变体。此代码生成完全相同的地图:

// Set up styles for subway lines
function subteStyle(feature) {
  var colorToUse;
  var line = feature.properties.LINEASUB;

  if (line === "LINEA A") colorToUse = "#46C7FA";
  else if (line === "LINEA B") colorToUse = "#E81D1A";
  else if (line === "LINEA C") colorToUse = "#4161BA";
  else if (line === "LINEA D") colorToUse = "#599C65";
  else if (line === "LINEA E") colorToUse = "#65018A";
  else if (line === "LINEA H") colorToUse = "#FAF739";
  else colorToUse = "#000000";

  return {
    "color": colorToUse,
    "weight": 5
  };
}

// Create layer and add it to the map
var subteLayer = L.geoJSON(subteData, {
  style: subteStyle
});

上面的示例使用函数子测试样式从每个特征读取属性LINEASUB,从而计算出地铁线路的名称(line a、LINEA B等)。应用If/then逻辑,根据地铁线路名称找到要使用的适当颜色。最后,此颜色应用于函数返回的样式。

8.2.3. 基于数字属性的比例符号

如果您的字段具有某些数字属性,例如地铁线路的长度或每天的乘客数量,则可能需要按比例调整符号的大小,以便用较大的符号表示较大的值。让我们考虑一个示例,其中有一个GeoJSON数据集,它显示了南美一些最大的地铁系统的统计数据。您将在本课程演练中更加熟悉此数据集,但这是基本的属性表和地图:

../../_images/metro_table.png

图8.3

../../_images/metro_basic.png

图8.4

在上图中,所有地铁系统均使用相同的符号绘制。但是,让我们假设您想按比例调整符号的大小,以使地铁系统中的车站越多,地图符号就越大。注意,STATIONS属性包含此信息。所需的地图如下所示:

../../_images/metro_proportional.png

图8.5

完成Leaflet中的比例符号需要您定义一些数学函数,该函数将根据站点的每个要素的属性值来调整每个符号的大小。下面的语法有些高级,但请注意读取车站值、除以80并乘以30的行,以获得任何给定地铁系统符号的宽度和高度(以像素为单位)。这些数字表明,80个车站的地铁系统的符号宽度将为30像素,较少车站的地铁系统的符号宽度将小于30像素,较多车站的地铁系统的符号宽度将大于30像素(数字80和30当然是完全任意的,可以根据您自己的数据进行调整):

// function to size each icon proportionally based on number of stations
function iconByStations(feature){
  var calculatedSize = (feature.properties.STATIONS / 80) * 30;

  // create metro icons
  return L.icon({
    iconUrl: 'metro.svg',
    iconSize: [calculatedSize, calculatedSize]
  });
}

// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {icon: iconByStations(feature)});
  }
});

在上面的代码中,iconSize是一个两项JavaScript数组,包含应用于图标的宽度和高度(以像素为单位)。另外,请注意pointToLayer属性的使用,如果要用自己的图形替换默认的 Leaflet 标记,则必须使用该属性。

8.2.4. 基于数字属性的数据类符号

在某些情况下,将数字数据分解成各种类别(由渐变颜色或类似方法表示)可能更合适。对于有时很难使用比例符号来概念化的线和面数据集而言,尤其如此。您可以根据等间隔,分位数,自然中断或其他任意方案来确定类的边界(在Esri软件中,您可能已经听说过这些类中断)。

例如,在下图中,100多个车站的地铁系统用深红色表示。具有50到100个站点的系统用红色表示。少于50个站点的系统用粉色表示:

../../_images/metro_classified.png

图8.6

为了在Leaflet中符号化数据类,我们将读取要素属性之一,然后使用if / then逻辑根据类中断对其进行检查。下面的代码定义了我们的地铁示例中使用的三个类。每个类都使用其唯一的红色色调引用不同的SVG符号:

// create metro icons
var metroLowIcon = L.icon({
  iconUrl: 'metro_low.svg',
  iconSize: [25,25]
});

var metroMediumIcon = L.icon({
  iconUrl: 'metro_medium.svg',
  iconSize: [25,25]
});

var metroHighIcon = L.icon({
  iconUrl: 'metro_high.svg',
  iconSize: [25,25]
});

// function to use different icons based on number of stations
function iconByStations(feature){
  var icon;
  if (feature.properties.STATIONS >= 100) icon = metroHighIcon;
  else if (feature.properties.STATIONS >= 50) icon = metroMediumIcon;
  else icon = metroLowIcon;

  return icon;
}

// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {icon: iconByStations(feature)});
  }
});

尽管上面的代码看起来很多,但请注意,其中一半只是设置图标。对线或多边形进行分类的函数可能要简单得多,因为可以根据感兴趣的属性使用不同的笔划或填充颜色定义单个样式。

如果您打算使用一个分类系统,如Jenks自然中断、等间隔、分位数等,则必须自己计算中断值(或find a library 在定义规则之前。您可以手动执行此操作,也可以添加更多JavaScript代码来动态计算值。

8.2.5. 基于属性限制特征可见性的方法

在某些情况下,您可能只想基于某些属性值或值组合显示数据集中要素的子集。(如果您熟悉Esri ArcMap,则称为“定义查询”。)假设您只想显示其COUNTRY属性编码为“巴西”的地铁系统:

../../_images/metro_brazil_only.png

图8.7

在 Leaflet 中创建图层时,可以直接在“过滤器”属性中执行此操作。在这里,您将编写一个函数,该函数计算一个特征并返回一个值true或false。 Leaflet 将只绘制返回true值的特征:

// create metro icons
var metroIcon = L.icon({
  iconUrl: 'metro.svg',
  iconSize: [25,25]
});


// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {
      icon: metroIcon
    });
  },
  filter: function(feature, layer) {
    if (feature.properties.COUNTRY === "Brazil") return true;
    else return false;
  }
});

上面示例中的filter函数测试COUNTRY属性等于“Brazil”的特性。注意,上面的示例只是读取一个属性,它不执行空间查询来查找巴西的边界。如果你能对数据进行预处理,在每个地铁系统上放置一个国家属性,而不是在网络环境中使用空间处理,那么你的网站将运行得更快。

现在,让我们看一个带有数字属性的场景。假设我们只想展示超过75个车站的地铁系统:

../../_images/metro_75_stations.png

图8.8

这可以通过以下过滤器实现:

// create metro icons
var metroIcon = L.icon({
  iconUrl: 'metro.svg',
  iconSize: [25,25]
});


// create the GeoJSON layer and call the styling function with each marker
var metroLayer = L.geoJSON(metroData,  {
  pointToLayer: function (feature, latlng) {
    return L.marker(latlng, {
      icon: metroIcon
    });
  },
  filter: function(feature, layer) {
    if (feature.properties.STATIONS > 75) return true;
    else return false;
  }
});

在上面的代码中,注意对大于75的桩号值的查询。

8.2.6. 结论

Leaflet提供了多种选项,用于基于属性值在客户端上符号化数据。您不应局限于整个图层都使用默认符号系统或单一符号类型。

创建 Leaflet 样式可能需要一些尝试和错误,以及一些熟练的调试。如果你从一个有效的例子开始,一个接一个地调整它,经常停下来测试你的修改,你会获得更大的成功。本课程的这一部分特意编写了大量代码,以便您可以使用许多示例来开始学习。