在GEE里被问得最多的数据类型,除了逐像元的Image之外,大概就是矢量那一家子了。FeatureCollection这个类型,我见过太多人一开始被它绕晕:明明就是个"点线面集合",为什么操作起来一会儿要map,一会儿要filter,还动不动就报错说"无法在Element上调用该函数"。其实如果你把FeatureCollection当成一张"带空间位置的表格"来理解,GEE里一大半的矢量操作逻辑就都顺了。这篇文章我就以FeatureCollection为主线,把它的概念、创建方式、属性筛选、渲染与导出整套流程讲透,尤其会把我这几年来踩过的坑一并列出来。适合刚开始学GEE的遥感、GIS从业者,也适合已经被filter和map折磨过一轮、想系统捋一遍的同学。
1. FeatureCollection究竟是什么:从一个Feature说起
1.1 Feature = Geometry + 属性的组合逻辑
很多教程一上来就给你摆ee.FeatureCollection的API文档,结果新手压根不知道这个对象内部长什么样。我的经验是,先搞明白Feature,再理解FeatureCollection就顺理成章。Feature这个类型在GEE里就做一件事:把一个几何对象(Geometry)和一堆属性(properties)捆绑在一起。
几何对象就是点、线、面,比如ee.Geometry.Point([lon, lat])是一个点,ee.Geometry.LineString([[lon1,lat1],[lon2,lat2]])是一条线。而Feature就是在这些几何上附加了一个字典,类似这样:
javascript复制var myFeature = ee.Feature(
ee.Geometry.Point([121.47, 31.23]),
{name: '观测点A', value: 35.6, time: '2024-06-01'}
);
这段代码创建了一个带两个字段(name、value)的"带位置记录"。属性字段不限制类型,字符串、数字、数组都可以放进去,唯一要注意的是GEE服务器的数据类型限制,嵌套结构别搞得过于复杂,否则后续序列化很容易出幺蛾子。Feature最方便的地方在于,你可以像操作普通对象一样,通过.get('name')去取属性值,也可以通过.set('value', 40)去更新属性,而几何对象始终跟着这条记录走,不会丢。
理解了Feature之后,FeatureCollection就好办了:它就是Feature的集合,存储方式类似于一个数组,内部每一条都带有形状不同的Geometry,以及结构一致或不一致的properties。不过GEE更希望你的集合里所有Feature的字段结构是统一的,因为很多聚合函数、筛选操作都是基于"整列字段"来做的,字段不齐会让后面统计时出现一堆null。
1.2 FeatureCollection的本质是一张"属性表"
我一直觉得FeatureCollection最恰当的类比,是GIS桌面软件里的属性表。你想想,在QGIS或ArcGIS里,一个矢量图层打开属性表,每一行是一个要素,每一列是一个字段,行里还隐含着这个要素的几何形状。FeatureCollection就是这样一张表,只是它跑在云端、面向海量数据,操作方式从鼠标点击换成了代码。
这个"表格"思维非常有用。比如你在QGIS里想按属性选要素,用"按属性选择"对话框;在GEE里就是fc.filter(ee.Filter.eq('字段名', 值))。你在QGIS里想做字段计算,在GEE里就是fc.map(function(f) { return f.set('新字段', 计算表达式); })。你在QGIS里想对某一列做统计,在GEE里就是fc.aggregate_mean('字段名')。一旦你把FeatureCollection当作属性表来用,GEE的矢量操作就不再是一堆孤立的API函数,而是一套有逻辑的表格处理流程。
也正是因为这一点,FeatureCollection和普通的JavaScript数组有天壤之别。你没法用for循环去遍历它,也没法直接用arr.push去追加元素。GEE里所有对FeatureCollection的操作都需要通过map、filter这类服务器端函数来完成,它们会被翻译成云端分布式计算任务。换句话说,你不是在操作一堆本地对象,而是在给一台"云端遥感计算引擎"下指令。
1.3 FeatureCollection与Image、Geometry的协作边界
在GEE里,数据类型之间不是孤立的,但它们的协作方式有明确边界。FeatureCollection是矢量侧的高层容器,Geometry是底层几何,Image是栅格侧的核心。实际项目中,矢量和栅格最常见的配合方式有这么几种:
- 用FeatureCollection去裁剪Image,做区域统计:image.reduceRegions({collection: fc, reducer: ee.Reducer.mean(), scale: 30})。
- 用Image的像素值去提取矢量点的属性:sampleRegions。
- 用FeatureCollection去筛选Image集合:ImageCollection.filterBounds(fc)。
这些操作里,FeatureCollection充当的是"空间范围"或者"采样点位"的角色。所以你可以看到,FeatureCollection不只是用来展示点线面的,它是连接矢量与栅格两大世界的桥梁。很多做地物分类的同学,整个工作流就是:先建样本点FeatureCollection,然后对每个样本点提取影像波段值,得到带特征字段的样本集,最后喂给分类器。分类器输出出来还是一个FeatureCollection,只是属性里多了一个"类别"字段。懂得让FeatureCollection在不同数据形态之间流转,GEE项目基本上就打通了一半。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 创建FeatureCollection的几种方式与关键参数
2.1 从零构造:ee.Feature与ee.FeatureCollection
最基础的方式,就是先构造单个Feature,再组成集合。直接上代码:
javascript复制var feat1 = ee.Feature(ee.Geometry.Point([121.47, 31.23]), {id: 1, type: 'obs'});
var feat2 = ee.Feature(ee.Geometry.Point([121.48, 31.24]), {id: 2, type: 'monitor'});
var fc = ee.FeatureCollection([feat1, feat2]);
print(fc);
这里有个细节值得注意:ee.Feature(geometry, properties)函数里,第二个参数是一个普通JavaScript对象,不是ee.Dictionary。不过你用ee.Dictionary包装一下也可以,GEE会自动转换。此外,如果属性值里有嵌套的数组或对象,建议提前测试一下,有些复杂结构在云端序列化时会被转换成奇怪的类型,等打印出来才傻眼。
如果你想直接从一个Geometry对象生成FeatureCollection,也是可以的:
javascript复制var poly = ee.Geometry.Polygon([[[110, 30], [111, 30], [111, 31], [110, 31]]]);
var fcFromGeom = ee.FeatureCollection(poly);
系统会自动把几何包装成一个没有属性的Feature。这种写法在做缓冲区分析、临时范围展示时非常顺手。但要注意:此时FeatureCollection里只有一个要素,没有属性列。
2.2 从Geometry列表批量生成
另一种常见需求是,你手上有一堆点坐标,想一次性生成多个点要素。新手容易犯的错是先用JavaScript的for循环一个个构建,然后push进数组,最后再转FeatureCollection。这种做法在小数据量下看着没问题,但它违背了GEE的"服务器端延迟计算"原则,一旦数据量变大,性能会很差,还可能触发GEE的某些内部限制。
正确姿势是用geometries参数直接传入一个几何列表:
javascript复制var points = [
ee.Geometry.Point([121.47, 31.23]),
ee.Geometry.Point([121.48, 31.24]),
ee.Geometry.Point([121.49, 31.25])
];
var fcPoints = ee.FeatureCollection(points);
print(fcPoints);
如果这些点坐标是存放在一个普通数组里的,比如从CSV读取经纬度后得到了坐标对,你可以先用Array.map转成ee.Geometry.Point的数组,再喂给ee.FeatureCollection。不过实际项目中,我更推荐不要自己造轮子,直接用GEE内置的表格导入方式——直接从CSV或SHP文件上传,省时省力,还不用处理坐标系问题。
实际上最暴力的方式,是直接用ee.FeatureCollection的构造器接收一个"包含GeoJSON的要素列表"。比如:
javascript复制var geojsonFC = ee.FeatureCollection([
{type: 'Feature', geometry: {type: 'Point', coordinates: [121.47, 31.23]}, properties: {name: 'A'}},
{type: 'Feature', geometry: {type: 'Point', coordinates: [121.48, 31.24]}, properties: {name: 'B'}}
]);
这种方式适合把外部JSON数据转成GEE对象,代码和GeoJSON标准完全对齐,几乎零学习成本。
2.3 从本地表格数据导入(CSV/Shapefile/GeoJSON)
在真实项目里,样本点、行政区边界、生态分区这些数据,通常不可能在代码里手写坐标,基本都是外部准备好的。GEE最常用的导入方式是Assets面板上传。在Code Editor左侧Assets里,点击"New"按钮,选择"Table upload",然后上传CSV、Shapefile(记得打包成zip)、GeoJSON或者KML。上传的时候会让你填写一个Asset ID,类似users/你的用户名/你的数据名,之后就可以在代码里引用了:
javascript复制var countries = ee.FeatureCollection('你的Asset完整路径');
这是最推荐的创建方式,尤其是数据量超过几千条时,把数据放进Assets比在代码里硬编码要稳定得多。另一种轻量方式是在代码里通过ee.FeatureCollection.fromTable或者ee.Table.parse...(旧版API)解析表格数据,但比较繁琐,我只在需要动态解析外部CSV时才会用。
这里必须提一个我在项目里反复踩过的坑:CSV上传时要特别注意几何列的定义。如果你上传的是带经纬度的CSV,GEE需要你指定X(经度)列和Y(纬度)列,并且CRS默认是EPSG:4326,也就是WGS84经纬度坐标。如果你在CSV里加了一列叫"lon",一列叫"lat",上传时会自动识别;但如果你的列名是中文或者稀奇古怪的缩写,上传面板里找不到对应字段,就会生成一堆没位置的要素。上传成功后,建议立刻打印一下集合大小和第一条要素,确认几何没有丢失。
2.4 构造时的关键参数与默认真相
创建FeatureCollection时,还有一个容易被忽略的参数是几何的"测地线"属性。GEE里点线面默认都是以大地线(geodesic)方式存储的,也就是说,两点之间的连线默认是沿地球椭球面的最短弧线,而不是平面上的直线。对大多数地图浏览场景来说,这个默认值没毛病;但如果你在做精确的面积计算或与小尺度局部数据叠加,可能会发现结果和ArcGIS里算的不一样,就是因为这个测地线设置。
如果你不希望线、面按大地线解释,可以在构造Geometry时显式指定:
javascript复制var line2D = ee.Geometry.LineString(
[[121.47, 31.23], [121.48, 31.25]],
{geodesic: false}
);
然后由这些几何生成的FeatureCollection自然也就不再是测地线几何了。同理,构造面时还有一个evenOdd参数,表示环方向规则,即多边形的内外环是通过绕行方向来区分的。普通场景用默认值就好,但如果你从外部导入的GeoJSON多边形偶尔出现挖洞异常,检查一下evenOdd设置往往能解决问题。
这些"默认真相"平时不显眼,一旦遇到跨平台数据互相调用,或者投影坐标系混用,就会变成排查问题的关键。我自己的习惯是,在创建任何FeatureCollection时,如果后续要用到面积/距离计算,先明确几何的CRS与测地线设置,而不是想当然用默认。
3. 核心属性操作:筛选、字段处理与统计
3.1 空间与时间筛选:filterBounds 与 filterDate
FeatureCollection最常用的功能之一就是筛选。筛选分两类,一类是按空间范围过滤,一类是按时间或属性过滤。
按空间范围过滤,最高频的写法是filterBounds:
javascript复制var roi = ee.Geometry.Polygon([[[110, 28], [115, 28], [115, 33], [110, 33]]]);
var filtered = fc.filterBounds(roi);
print(filtered);
filterBounds的逻辑很简单:返回那些几何与指定范围有相交的元素。注意这里用的是"相交"判定,不需要完全包含。所以如果你的点是落在范围边界上,也会被保留。与之类似的还有filterMetadata、filterEq这些老朋友,但filterBounds最常用,而且它在服务器端有空间索引优化,速度通常不错。一个小建议是,如果ROI很大,且原始FeatureCollection特别大,可以先给ROI做一个缓冲或简化,避免不必要的计算。
按时间过滤,直接调用filterDate:
javascript复制var fcInTime = fc.filterDate('2020-01-01', '2020-12-31');
这里要求你的FeatureCollection里含有date字段,否则过滤结果为空。GEE内部会自动识别属性里名为date、startDate之类的字段,但我建议还是显式构造一个标准字段,用ee.Date.parse转成时间对象后统一存储。
3.2 属性过滤:ee.Filter 从简单到组合
属性过滤的核心是ee.Filter。新手容易把ee.Filter和filter方法的名字混着用,其实流程是这样:构造一个Filter对象,然后传给filter方法。举个例子:
javascript复制var hotFC = fc.filter(ee.Filter.gt('value', 30));
var rangeFC = fc.filter(ee.Filter.rangeContains('value', 20, 40));
如果你要同时满足多个条件,用ee.Filter.and或逻辑运算:
javascript复制var bothFC = fc.filter(
ee.Filter.and(
ee.Filter.gt('value', 30),
ee.Filter.stringStartsWith('name', 'A')
)
);
GEE的ee.Filter家族特别大,常用的有eq、neq、gt、gte、lt、lte、rangeContains、stringStartsWith、stringEndsWith、listContains、inList。有一个值得记住的技巧是用ee.Filter.inList做批量筛选,比如你想保留多个指定日的记录,一条一条or写太啰嗦,直接inList('date', ['2020-01-01', '2020-02-01'])就搞定。
属性筛选的性能通常比空间筛选更好,因为它不涉及几何运算。所以能先用属性筛,再用空间筛,是比较推荐的执行顺序——先把记录数降下来,再做边界相交判断,云端计算量和传输量都会小很多。
3.3 字段处理:map、set、select、rename 的组合拳
当需要对每条记录属性进行逐条修改时,最常用的就是map。map的含义是"遍历集合,对每个Feature执行某个函数,返回新的Feature,最终拼成新的FeatureCollection"。比如给每个点新增一个字段:
javascript复制var fcNew = fc.map(function(f) {
var val = ee.Number(f.get('value'));
return f.set('value2', val.multiply(2));
});
注意,map里的函数必须返回一个Feature,如果你在函数里做了条件分支,务必保证所有分支都返回Feature,否则报错信息会非常绕。
还有一种常见需求是只保留某些字段,用select:
javascript复制var fcSelected = fc.select(['name', 'value']);
select会丢掉其他所有属性,只保留你指定的列。这在导出之前清理属性、缩减数据体积时非常有用。另一个操作是rename,用来给字段起别名:
javascript复制var fcRenamed = fc.select(['name', 'value'], ['名称', '数值']);
select和rename放到一起用,是同一次字段重命名的两种表达。如果字段名里有中文,或者需要对接外部系统的固定列名,这个操作几乎是必须的。
3.4 聚合统计:aggregate_* 系列速查
属性表最强大的地方,在于可以快速做整列统计。用aggregate_*系列函数,可以免去map+reduce的繁琐过程。我把常用的几个列出来:
| 函数 | 作用 | 说明 |
|---|---|---|
| aggregate_count | 统计非空记录数 | 等价于.size() |
| aggregate_sum | 求和 | 属性必须可转数字 |
| aggregate_mean | 求平均 | 同上 |
| aggregate_min / max | 求极值 | 数值型属性 |
| aggregate_histogram | 字段值频次统计 | 返回字典,适合类别字段 |
| aggregate_countDistinct | 去重后的记录数 | 类别字段常用 |
| aggregate_array | 返回字段值数组 | 适合后续数组处理 |
举个例子,统计所有点的属性value总和:
javascript复制var total = fc.aggregate_sum('value');
print('total value:', total);
它返回的是一个数字,不需要再用reduce。这些函数还有对应的percentile版本,比如aggregate_interval('value', 90),可以返回一个区间,做法物分类样本筛选时非常实用。如果你要多个统计量,可以分开调用,也可以配合reducer更精细地处理。在实际项目中,我很少手写reduceRegion来处理FeatureCollection的统计,aggregate_*已经完全够用。
3.5 排序、去重与抽样
除了筛选和字段计算,日常还经常碰到三类需求:按字段排序、去除重复记录、随机抽样。排序用sort:
javascript复制var sorted = fc.sort('value', false); // 第二个参数表示降序,默认false为升序
去重用distinct:
javascript复制var distinctDates = fc.distinct('date');
如果你希望基于某个字段去重并且保留某些记录,distinct支持的字段参数正好能用上。随机抽样用randomColumn之后结合limit,比如:
javascript复制var fcWithRandom = fc.randomColumn('random');
var sample = fcWithRandom.sort('random').limit(100);
先给每条记录加一个随机数列,再按随机数列排序,最后取前N条,这是GEE里最常见的均匀抽样方式。如果你想按比例抽样,可以用随机数列加filter,例如设置保留概率0.2,就是过滤random < 0.2的记录。这个方法比limit更灵活,抽样结果也接近完全随机。
4. FeatureCollection可视化与导出:结果呈现的几种姿势
4.1 矢量图层在地图上的渲染技巧
FeatureCollection作为矢量数据,最常见的展示方式就是Map.addLayer。默认情况下,如果不指定任何样式参数,GEE会随机给要素分配颜色,这在快速浏览时够用,但一旦要出图或者做分类专题,就必须手动控制样式。基础写法如下:
javascript复制Map.centerObject(fc, 10);
Map.addLayer(fc, {color: 'blue'}, '原始点');
对于点要素,你可以设置pointSize来控制点的大小,设置pointShape选择点的形状(默认是圆形)。对于线要素,color控制颜色,width控制线宽。对于面要素,color控制填充颜色,还可用color的透明度控制视觉效果。不过有个很重要的细节:在GEE的Map.addLayer参数里,面要素的color是填充色,而边线颜色是用outlineColor来控制的。很多人以为传一个color就是全部,结果面要素的边线变成默认黑色,出图效果很突兀。
javascript复制Map.addLayer(fcPoly, {color: '255, 0, 0, 0.5', outlineColor: '000000'}, 'polygon layer');
这个写法里color用的是RGBA的字符串形式,前三个值是红绿蓝,最后是透明度,范围0到1。透明度设置非常重要,尤其是多个面图层叠加时,不设透明度,下层信息全被挡住。点图层的默认样式很简单,建议加点pointSize,视觉上更清晰。
4.2 按属性着色:style函数的进阶玩法
如果你需要按属性值给要素上色,比如按某个连续字段的大小显示不同颜色,直接用Map.addLayer的参数就不够了。GEE提供了两种方案,一种比较老的style()函数,需要在FeatureCollection上调用:
javascript复制var styled = fc.style({
color: '01000000', // 填充颜色,这里的8位十六进制实际是ARGB
pointSize: 8,
pointShape: 'circle',
styleProperty: 'value',
styleValue: 'value'
});
Map.addLayer(styled, {}, 'styled points');
另一种是较新推荐的Map.addLayer的style参数方式。我更推荐把样式设为FeatureCollection的一个属性列,然后在addLayer里指定styleProperty。举例来说,你可以先map一个函数,给每个Feature新增一个"color"字段,写入不同的颜色字符串,然后再用style参数渲染:
javascript复制var fcStyled = fc.map(function(f) {
var val = ee.Number(f.get('value'));
var color = val.gt(30) ? 'red' : 'green';
return f.set('style', {color: color, pointSize: 6});
});
Map.addLayer(fcStyled, {styleProperty: 'style'}, 'styled by value');
这种按属性着色的思路,在展示监测站点污染等级、分类样本点类别等场景中尤其好用。它也符合GEE"把样式作为数据的一部分"的理念,方便将样式信息随FeatureCollection一起导出。不过注意,style属性在导出到外部GIS软件时通常没有意义,建议导出前再select掉。
4.3 数据导出的正确打开方式(toDrive/toAsset)
最后,FeatureCollection的成果需要导出。最常用的是Export.table.toDrive,把矢量数据导出为GeoJSON、CSV或Shapefile(需要指定格式),也可以直接导出为SHP压缩包:
javascript复制Export.table.toDrive({
collection: fcStyled,
description: 'export_my_points',
folder: 'GEE_export',
fileNamePrefix: 'my_points',
fileFormat: 'SHP'
});
如果你想把中间结果保存到Assets以便下次快速调用,用Export.table.toAsset:
javascript复制Export.table.toAsset({
collection: fcStyled,
description: 'save_to_asset',
assetId: 'users/你的用户名/my_points'
});
这里有几个要点:description不能有空格和特殊字符,否则任务创建失败;folder是Drive里的文件夹名,如果不存在,GEE会在导出时自动创建;fileFormat支持CSV、GeoJSON、SHP、KML等,导出表格时尽量用GeoJSON或SHP,CSV虽然方便,但会丢掉几何信息。如果你想要Excel能直接打开的表格,导出CSV时可以用zip选项,或者直接导出为CSV后自己再转。
导出任务运行一般需要几分钟到几十分钟,取决于数据量和GEE服务器负载。建议导出前先print或Map查看一下数据是否满足预期,不要等导出完了才发现属性列名错乱。
5. 高频故障与排查:我踩过的那些坑
5.1 筛选结果总是空的元凶:经纬度顺序
我敢打赌,GEE新手最崩溃的瞬间,就是filterBounds返回了空集合,或者画出来的点跑到了完全错误的位置。排查这个问题的第一步,永远是核查你输入的坐标到底是[经度, 纬度]还是[纬度, 经度]。
GEE遵循GeoJSON标准:坐标顺序是[经度, 纬度],也就是[lon, lat]。国内很多同学习惯先写纬度再写经度,写坐标对时顺手就是[30, 110],结果点就飞到了海洋或者异常区域。这个问题在代码里非常隐蔽,因为GEE不会报错,打印点坐标时也显示得很正常,只有叠加到底图上才露馅。排查技巧是:把Map.centerObject设为你构造的点,然后看地图中心到底在哪个位置。
如果你的数据来自CSV,尤其要注意上传面板里的经纬度列顺序,它会要求你x列是经度、y列是纬度。有些数据源导出CSV时字段名写的是"lat, lon",如果你不加辨别就按自然顺序填写,GEE会把纬度当经度用,点全部错位。
5.2 filterDate对字符串日期失效
filterDate看起来简单,但它要求你要过滤的字段真正存储为日期类型,而不是字符串。很多从外部表格导入的FeatureCollection,日期字段默认会被当成字符串,就算你写的是2024-06-01这种标准日期格式,GEE也没法自动识别为日期。这时候直接filterDate('2024-01-01', '2024-12-31'),结果一定是空集合。
解决办法是在数据导入或创建后,先做一个转换:
javascript复制var fcFixed = fc.map(function(f) {
return f.set('date', ee.Date.parse('yyyy-MM-dd', f.get('date')));
});
这里ee.Date.parse的第一个参数是日期格式,第二个参数是字符串值。转换完成后再filterDate,就正常了。如果日期字符串格式很多样,建议统一处理好再入集合,否则后续每次用都要重复转换。我自己的习惯是,在创建FeatureCollection的第一步就规范字段类型,后续所有操作都建立在一套干净的数据结构上,能省掉无数隐藏bug。
5.3 Map.addLayer卡顿与视图加载慢
FeatureCollection动辄几万几十万个点,直接Map.addLayer,浏览器端很容易卡死。这其实是GEE的前端渲染限制,它不是计算瓶颈,而是客户端渲染瓶颈。应对办法有两个方向:一是数据抽样后再显示,二是用聚合生成密度网格来代替原始点。
抽样显示最简单:
javascript复制Map.addLayer(fc.randomColumn().sort('random').limit(5000), {color: 'red'}, 'sample 5000');
如果要展示所有点的空间分布,建议用reduceToImage或者先用reducer把点聚合到网格上,再用栅格底图方式展示。例如:
javascript复制var density = fc.reduceToImage({
properties: ['value'],
reducer: ee.Reducer.mean()
});
Map.addLayer(density, {min: 0, max: 50, palette: ['blue', 'yellow', 'red']}, 'grid mean');
reduceToImage会把矢量点按投影格网聚合生成栅格,既减轻了前端负担,又能可视化大样本的空间格局。这种方式在做人口分布、采样点密度时特别实用,比硬渲染几万个点要舒服得多。
5.4 属性值的null陷阱与类型强制转换
最后一个常见坑,是属性值里的null或类型不统一。比如你在feature1里设置了value为35.6,但feature2的value是字符串"35.6",那么后面做aggregate_sum、聚合统计或者比较筛选时,结果很可能不符合预期,甚至会直接报错。
GEE对属性类型比较敏感,尤其是数字类型。我的建议是,所有数字字段在进入FeatureCollection之前,统一用ee.Number包一层,比如:
javascript复制var feat = ee.Feature(geometry, {
value: ee.Number.parse(data_value),
year: ee.Number(year),
});
在做数值类型强制转换时,常用的是ee.Number.parse()方法,处理字符串转数字非常可靠。另一个方法是在map函数里用ee.Number(f.get('value'))统一转换,这样即使原字段是数字或字符串,都能得到一个数字。对于可能缺失的值,用ee.Filter.notNull(['value'])先过滤掉空记录,再执行聚合,能有效避免null带来的边界问题。
另外,如果你使用distinct或者sort,但字段类型不统一(有的是数字有的是字符串),GEE的排序结果可能完全违背直觉。这时候同样需要先统一类型。别嫌麻烦,字段类型规范在GEE项目里是性价比最高的习惯。
写在首篇之后
这篇内容是基于FeatureCollection最常用、最核心的知识点来展开的。按照我个人的经验,你在实际项目里遇到的大部分矢量处理需求,都能通过上面这些方法组合实现。比如做一个"按行政区统计站点数量"的任务,思路就是:先构造站点点集,然后关联到行政区面集,再按行政区域做分组统计,最后输出表格。这些操作涉及的空间关联(join)、分组统计(reduce)还没有展开讲,我准备在FeatureCollection系列的下篇里专门拆解,尤其是ee.Join的几种用法和group reducer的实战场景。如果你刚接触GEE,先把这篇文章里的创建、筛选、字段处理、导出这些基本功练熟,再往深走会顺畅很多。
