自定义View进阶篇(多边形各属性值分布图)


先来个效果图:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e4d7cb"
    tools:context="amusement.czq_901201.cn.com.amusement.MainActivity">

    <amusement.czq_901201.cn.com.amusement.PolygonsView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />
</RelativeLayout>


再来分享一下源码


 
</pre><pre name="code" class="java">package amusement.czq_901201.cn.com.amusement;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static android.R.attr.width;

/**
 * Created by Administrator on 2016/11/8.
 */

public class PolygonsView extends View {
    private Context context;
    /**
     * 六边形中心点x
     */
    private int cx;
    /**
     * 六边形中心点y
     */
    private int cy;
    /**
     * 父控件的宽度
     */
    private float pWidth;
    /**
     * 在外层7变形的半价
     */
    private int outR;
    /**
     * 线宽
     */
    private int stroke;
    private Paint paint;
    /**
     * 指标名称
     */
    private List<String> fightNames;
    /**
     * 指标值(与名称对应)
     */
    private List<Double> fightValues;
    /**
     * 进度条长度
     */
    private int progressWidth;

    public PolygonsView(Context context) {
        super(context);
        inits(context);
    }


    public PolygonsView(Context context, AttributeSet attrs) {
        super(context, attrs);
        inits(context);
    }

    public PolygonsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        inits(context);
    }

    /**
     * 初始化
     */
    private void inits(Context context) {
        this.context = context;
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeWidth(dip2px(3));
        paint.setStyle(Paint.Style.STROKE);
        //TODO 模拟数据
        fightNames = new ArrayList<>();
        fightValues = new ArrayList<>();
        Collections.addAll(fightNames, "财富", "人脉", "颜值", "睡觉", "吃饭", "吹牛", "做梦");
        Collections.addAll(fightValues, 77.7, 21.3, 31.1, 91.1, 78.0, 62.0, 33.9);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //画中心间隔线
        drawCenterLine(canvas);
        //画矩形
        drawRecT(canvas);
        //画文字
        drawText(canvas);
        //画各项值
        drawLine(canvas);
        //画进度条
        drawProgressBar(canvas);
        super.onDraw(canvas);
    }

    /**
     * 画进度条
     *
     * @param canvas
     */
    private void drawProgressBar(Canvas canvas) {
        paint.setColor(Color.parseColor("#FFA2A0A4"));
        paint.setStrokeWidth(dip2px(1));
        Rect rect = new Rect();
        //返回包围整个字符串的最小的一个Rect区域
        paint.getTextBounds("财富", 0, 1, rect);
        int strwid = rect.width();
        int strhei = rect.height();
        int startTextX = dip2px(12);
        int startTextY = cy+outR+dip2px(45);
        int startLineX = startTextX+strwid+dip2px(30);
        int startLineY = startTextY-strhei/3;
        int marginTop = dip2px(10)+strhei;
        for (int i = 0; i < 7; i++) {
            canvas.drawText(fightNames.get(i),startTextX,startTextY+marginTop*i,paint);
            canvas.drawLine(startLineX,startLineY+marginTop*i,startLineX+progressWidth,startLineY+marginTop*i,paint);
        }
        paint.setColor(Color.parseColor("#FFE70A0E"));
        paint.setStrokeWidth(4);
        float progressX = 0; float progressY = 0;float r = dip2px(3);
        for (int i = 0; i < 7; i++) {
            progressX = (float) (startLineX+progressWidth*fightValues.get(i)/100.0);
            progressY = startLineY+marginTop*i;
            canvas.drawLine(startLineX,progressY, progressX,progressY,paint);
            canvas.drawCircle(progressX,progressY,r,paint);
        }
    }

    /**
     * 画各项值
     *
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        paint.setColor(Color.parseColor("#FFE70A0E"));
        paint.setStrokeWidth(dip2px(2));
        Path path = new Path();
        path.moveTo(cx, cy - getR(0));
        path.lineTo((float) (cx + getR(1) * Math.sin(getFuDu())), (float) (cy - getR(1) * Math.cos(getFuDu())));
        path.lineTo((float) (cx + getR(1) * Math.sin(getFuDu())), (float) (cy - getR(1) * Math.cos(getFuDu())));
        path.lineTo((float) (cx + getR(2) * Math.cos(getFuDu() * 2 - Math.PI / 2)), (float) (cy + getR(2) * Math.sin(getFuDu() * 2 - Math.PI / 2)));
        path.lineTo((float) (cx + getR(3) * Math.cos(getFuDu() * 3 - Math.PI / 2)), (float) (cy + getR(3) * Math.sin(getFuDu() * 3 - Math.PI / 2)));
        path.lineTo((float) (cx - getR(4) * Math.cos(getFuDu() * 3 - Math.PI / 2)), (float) (cy + getR(4) * Math.sin(getFuDu() * 3 - Math.PI / 2)));
        path.lineTo((float) (cx - getR(5) * Math.cos(getFuDu() * 2 - Math.PI / 2)), (float) (cy + getR(5) * Math.sin(getFuDu() * 2 - Math.PI / 2)));
        path.lineTo((float) (cx - getR(6) * Math.sin(getFuDu())), (float) (cy - getR(6) * Math.cos(getFuDu())));
        path.close();
        canvas.drawPath(path, paint);
    }

    /**
     * 通过下标获取改点到中心的距离
     *
     * @param i
     * @return
     */
    private float getR(int i) {
        return (float) (fightValues.get(i) / 100 * outR + 0.5f);
    }


    /**
     * 画文字
     *
     * @param canvas
     */
    private void drawText(Canvas canvas) {
        paint.reset();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#5d5d5d"));
        paint.setTextSize(dip2px(16));
        //Android中获取字符串长度、宽度(所占像素宽度)
        Rect rect = new Rect();
        //返回包围整个字符串的最小的一个Rect区域
        paint.getTextBounds("财富", 0, 1, rect);
        int strwid = rect.width();
        int strhei = rect.height();
        canvas.save();
        for (int i = 0; i < 7; i++) {
            canvas.drawText(fightNames.get(i), cx - strwid, cy - outR - strhei / 2, paint);
            canvas.rotate(360 / 7.0f, cx, cy);
        }
        canvas.restore();
    }

    /**
     * 画矩形
     *
     * @param canvas
     */
    private void drawRecT(Canvas canvas) {
        paint.setStrokeWidth(dip2px(1));
        /*设置paint 的style为 FILL:实心*/
        paint.setStyle(Paint.Style.FILL);
        canvas.save();
        for (int i = 0; i < 7; i++) {
            canvas.rotate(360 / 7.0f, cx, cy);
            //方向:由外向内
            //第一层
            paint.setColor(Color.parseColor("#aaf4df"));
            Path path = new Path();
            path.moveTo(cx, cy - outR);
            path.lineTo((int) (cx + Math.sin(getFuDu()) * outR + 0.5f), (int) (cy - outR * Math.cos(getFuDu()) + 0.5f));
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke) + 0.5f), (int) (cy - (outR - stroke) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo(cx, cy - outR + stroke);
            path.close();
            canvas.drawPath(path, paint);
            //第二层
            paint.setColor(Color.parseColor("#74e294"));
            path = new Path();
            path.moveTo(cx, cy - outR + stroke);
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke) + 0.5f), (int) (cy - (outR - stroke) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke * 2) + 0.5f), (int) (cy - (outR - stroke * 2) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo(cx, cy - outR + stroke * 2);
            path.close();
            canvas.drawPath(path, paint);
            //第三层
            paint.setColor(Color.parseColor("#e28062"));
            path = new Path();
            path.moveTo(cx, cy - outR + stroke * 2);
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke * 2) + 0.5f), (int) (cy - (outR - stroke * 2) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke * 3) + 0.5f), (int) (cy - (outR - stroke * 3) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo(cx, cy - outR + stroke * 3);
            path.close();
            canvas.drawPath(path, paint);
            //第四层
            paint.setColor(Color.parseColor("#FFEC78A8"));
            path = new Path();
            path.moveTo(cx, cy - outR + stroke * 3);
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke * 3) + 0.5f), (int) (cy - (outR - stroke * 3) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo((int) (cx + Math.sin(getFuDu()) * (outR - stroke * 4) + 0.5f), (int) (cy - (outR - stroke * 4) * Math.cos(getFuDu()) + 0.5f));
            path.lineTo(cx, cy - outR + stroke * 4);
            path.close();
            canvas.drawPath(path, paint);
        }
        canvas.restore();
    }

    /**
     * 获取弧度
     *
     * @return
     */
    private double getFuDu() {
        return Math.PI * 2 / 7;
    }

    /**
     * 画中心间隔线
     *
     * @param canvas
     */
    private void drawCenterLine(Canvas canvas) {
        paint.setColor(Color.GREEN);

        canvas.save();
        for (int i = 0; i < 7; i++) {
            canvas.rotate(360 / 7.0f, cx, cy);
            canvas.drawLine(cx, cy, cx, cy - stroke * 4, paint);
        }
        canvas.restore();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        pWidth = getMeasuredWidth();
        cx = (int) (pWidth / 2.0 + 0.5f);
        progressWidth = (int) (pWidth * 4.0 / 5 + 0.5f);
        outR = (int) (pWidth*2.0 / 7.0 + 0.5f);
        cy = outR+dip2px(35);
        stroke = (int) (outR / 4.0 + 0.5f);
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public int dip2px(float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px 的单位 转成为 dp
     */
    public int px2dip(float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }


}

我想在GEE中分析一下昆嵛山地区2010-2020年的植被NDVI分布及长时空序列分析,需要绘制出NDVI分布图以及每个月的NDVI曲线,请帮我看一下下面的GEE代码是否可行,并进行改正 // 定义研究区域(昆嵛山范围,需要根据实际坐标修改) var kunyuMountains = ee.Geometry.Polygon( [[[121.8, 37.2], [121.8, 37.4], [122.1, 37.4], [122.1, 37.2]]], null, false); // 多边形坐标需要根据实际研究区修改 // 加载MOD13Q1 NDVI数据集(250m分辨率,16天合成) var ndviCollection = ee.ImageCollection('MODIS/006/MOD13Q1') .filterDate('2010-01-01', '2020-12-31') // 时间过滤 .filterBounds(kunyuMountains); // 空间过滤 // 定义NDVI可视化参数 var ndviParams = { min: -2000, max: 10000, palette: ['FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718', '74A901', '66A000', '529400', '3E8601', '207401', '056201', '004C00', '023B01', '012E01', '011D01', '011301'] // NDVI色带 }; // 计算年度平均NDVI并显示 var annualComposite = ndviCollection.select('NDVI') .mean() // 计算多年平均 .multiply(0.0001); // MODIS NDVI缩放因子 Map.centerObject(kunyuMountains, 9); // 地图中心定位 Map.addLayer(annualComposite.clip(kunyuMountains), ndviParams, 'Annual NDVI'); // 显示年度合成 // 时间序列分析 var monthlyNDVI = ndviCollection.map(function(image) { // 将时间戳转换为月份格式 var date = ee.Date(image.get('system:time_start')); var month = date.format('YYYY-MM'); // 格式化为年-月 // 返回带有月份属性的NDVI图像(已应用缩放因子) return image.select('NDVI') .multiply(0.0001) // 应用缩放因子转换为实际NDVI值 .set('month', month); }); // 创建月度时间序列图表 var chart = ui.Chart.image.series({ imageCollection: monthlyNDVI.select('NDVI'), region: kunyuMountains, reducer: ee.Reducer.mean(), scale: 250, // 匹配MODIS分辨率 xProperty: 'month' // 使用自定义的月份属性 }).setOptions({ title: '2010-2020 Monthly NDVI Variation', hAxis: {title: 'Date'}, vAxis: {title: 'NDVI'}, lineWidth: 2, pointSize: 3 }); print(chart); // 打印图表 // 导出年度NDVI结果到Google Drive(可选) Export.image.toDrive({ image: annualComposite.clip(kunyuMountains), description: 'Annual_NDVI_2010_2020', scale: 250, region: kunyuMountains, maxPixels: 1e13, fileFormat: 'GeoTIFF' }); // 附加说明: // 1. 实际使用时需要根据研究区修改多边形坐标 // 2. NDVI实际范围应为-0.2到1,但MODIS原始数据存储为整型需要乘以0.0001 // 3. 图表可能需要较长时间加载,取决于研究区大小 // 4. 导出数据前需要运行导出任务并授权
最新发布
04-03
<think>嗯,用户想在GEE里分析昆嵛山2010-2020年的NDVI分布和时空序列,画分布图和月曲线。他们提供的代码大致可行,但有几个地方需要调整。首先,我得仔细看看他们的代码结构。 他们定义了研究区域,用的是多边形坐标,不过坐标可能需要确认是否正确。昆嵛山的实际范围可能需要更精确的坐标,这点用户已经提到需要修改,所以可能没问题。接下来加载MOD13Q1数据集,时间过滤是对的,2010到2020年,空间过滤也正确。 然后计算年度平均NDVI,这里用了mean(),但要注意MOD13Q1是16天合成的数据,直接取平均可能会包含多个年份的同期的数据,这样年度平均可能需要先按年分组,再取平均。比如,用户可能想要每年的平均,然后多年平均,或者是整个时间段的总平均?原代码中直接用了mean(),这会计算所有影像的平均,可能不是分年度的。但用户的需求是“年度平均”,可能需要先按年聚合,然后取各年的平均。不过用户的需求是绘制整个时期的分布图,可能没问题,但如果是多年平均的话,这样处理是对的。不过原代码注释说是“年度平均”,可能需要确认。 可视化参数方面,min和max设置的是-2000到10000,但乘以0.0001后实际范围是-0.2到1,所以色带的对应可能需要调整。不过用户已经注意到缩放因子,所以这部分没问题。但可视化参数中的min和max应该设置为缩放后的范围,即-0.2和1,或者保持原始数据的范围,但应用缩放后的图像。这里可能有错误,因为原代码中的ndviParams的min和max还是原始值,而annualComposite已经乘以0.0001了。所以可视化参数应该调整到实际NDVI的范围,即min: -0.2, max: 1。否则,显示的颜色可能不正确,因为图像值已经缩放了,但可视化参数还是原数据范围。 接下来是时间序列分析,用户希望每个月的NDVI曲线。代码中将每个影像的月份属性设置为YYYY-MM,然后用这些属性作为x轴。但MOD13Q1是16天合成的数据,每个月可能有1-2个影像。直接按月份分组的话,可能需要将同月的影像进行平均,否则图表上每个月的点会有多个,导致折线图波动大。原代码中monthlyNDVI只是给每个影像添加了月份属性,但没有按月份聚合。所以在创建图表时,imageCollection中的每个影像都是单独的,x轴是按月份,但同一月份可能有多个点,导致图表不连续。应该先按月份分组,计算每个月的平均值,再生成时间序列。 例如,使用group by 'month'来聚合同月的影像,取均值。否则,图表中的每个点代表一个影像,而不是每个月的平均值。这样会导致图表中有多个点在同一个月,折线会来回波动,无法准确反映月度趋势。因此需要修改时间序列部分,使用分组和聚合。 另外,在创建图表时,xProperty用了自定义的'month'属性,但ee.Chart.image.series默认使用system:time_start,这里可能需要确保月份属性正确,并且排序正确。或者,可能需要将月份转换为日期对象,以便正确排序,否则可能因为月份字符串的排序问题导致图表顺序错乱。例如,YYYY-MM格式的字符串在排序时是正确的,但作为分类变量可能不会被正确识别为时间序列。可能需要将月份转换为实际的日期,比如每个月的第一天,作为x轴的值,这样图表能正确按时间顺序排列。 另外,MOD13Q1的波段名称是否正确?原代码中使用的是'NDVI',而MOD13Q1的NDVI波段全名可能需要确认。比如,是否是 'NDVI' 还是 '250m 16 days NDVI'?需要检查数据集中的波段名称,避免出现错误。例如,在GEE中,MOD13Q1的NDVI波段全称是 'NDVI',所以代码中的select('NDVI')是对的。 导出部分没有问题,但用户可能需要调整导出参数,比如文件名、文件夹等。 总结需要修改的部分: 1. 可视化参数ndviParams的min和max应该设置为缩放后的范围(-0.2到1),而不是原始的-2000和10000。 2. 时间序列分析部分需要按月份聚合影像,计算每个月的平均NDVI,而不是直接使用每个影像的数据。 3. 可能需要将月份转换为日期对象,以确保图表按时间顺序正确排列。 4. 检查MOD13Q1的NDVI波段名称是否正确。 接下来一步步修改: 首先,修正ndviParams的min和max: 原代码中: min: -2000, max: 10000, 改为: min: -0.2, max: 1, 因为已经用multiply(0.0001)将数据缩放了。 其次,处理时间序列的月度聚合: 当前的monthlyNDVI只是给每个影像添加了月份属性,但没有聚合。需要将影像集合按月份分组,然后对每个月的影像取平均,生成一个月度平均的影像集合。 可以这样修改: 使用filterDate来遍历每个月,或者用group by 'month'。不过GEE中更高效的方式可能是将每个影像的月份作为属性,然后使用group()函数按月份分组,再对每个组求均值。 代码示例: var monthlyNDVI = ee.ImageCollection.fromImages( // 生成所有月份的范围 ee.List.sequence(0, 10*12-1).map(function(n) { // 2010-2020共11年,即132个月,这里可能需要调整 var startDate = ee.Date('2010-01-01').advance(n, 'month'); var endDate = startDate.advance(1, 'month'); // 筛选该月的影像 var monthlyImages = ndviCollection.filterDate(startDate, endDate); // 计算该月的平均NDVI,并设置月份属性 var monthlyMean = monthlyImages.select('NDVI').mean().multiply(0.0001) .set('system:time_start', startDate.millis()) .set('month', startDate.format('YYYY-MM')); return monthlyMean; }) ).filter(ee.Filter.neq('NDVI', null)); // 去除没有数据的月份 然后,创建图表时使用这个monthlyNDVI集合,并设置xProperty为'system:time_start',这样图表会自动按时间排序。 但原来的代码中用户可能想用自定义的month属性,但这样可能导致排序问题。更好的方法是使用system:time_start作为x轴,这样图表能正确识别时间序列。 所以修改后的时间序列部分: // 时间序列分析:生成月度平均NDVI var startYear = 2010; var endYear = 2020; var startMonth = ee.Date(startYear + '-01-01'); var totalMonths = (endYear - startYear + 1) * 12; // 计算总月数 var monthlyMeans = ee.ImageCollection( ee.List.sequence(0, totalMonths - 1) .map(function(n) { var currentDate = startMonth.advance(n, 'month'); var monthStart = currentDate; var monthEnd = currentDate.advance(1, 'month'); // 获取该月所有影像 var monthlyImages = ndviCollection.filterDate(monthStart, monthEnd); // 计算月平均,并设置时间属性 var meanImage = monthlyImages.select('NDVI').mean() .multiply(0.0001) .set('system:time_start', monthStart.millis()) .set('month', currentDate.format('YYYY-MM')); return meanImage; }) ).filter(ee.Filter.neq('NDVI_mean', null)); // 过滤掉无数据的月份 然后创建图表时,使用这个monthlyMeans集合: var chart = ui.Chart.image.series({ imageCollection: monthlyMeans.select('NDVI'), region: kunyuMountains, reducer: ee.Reducer.mean(), scale: 250, xProperty: 'system:time_start' // 使用系统时间属性 }).setOptions({ title: '2010-2020 Monthly NDVI Variation', hAxis: {title: 'Date', format: 'YYYY-MM'}, // 设置日期格式 vAxis: {title: 'NDVI'}, lineWidth: 2, pointSize: 3 }); 这样处理可以确保每个月的平均NDVI被正确计算,并且时间轴顺序正确。 此外,原来的代码中,annualComposite是计算所有年份的平均,可能用户需要的是整个研究期的平均NDVI分布图,这样是正确的。但如果用户想要每年一个平均,则需要分年处理,但根据问题描述,用户只需要分布图和月度曲线,所以原代码的年度合成部分可能没问题。 总结需要修改的地方: 1. 修正可视化参数中的min和max。 2. 重新处理时间序列,按月份聚合影像,生成月度平均。 3. 使用system:time_start作为图表x轴,确保正确排序。 4. 检查波段名称是否正确。 其他可能的问题: - 导出时annualComposite是否正确?是的,已经clip到区域。 - 坐标是否正确?用户需自行确认。 - 色带是否适合NDVI的范围?可能需要调整颜色方案,但原代码的色带是GEE中常用的NDVI色带,但缩放后的值可能需要调整min和max,所以修正min和max后色带应该正确显示。 修改后的代码应该能正确显示年度NDVI分布图,并生成平滑的月度时间序列曲线,每个点代表该月的平均NDVI。</think>以下是针对您代码的修改建议和优化方案,分步骤说明: $\textcolor{green}{一、可视化参数修正}$ ```javascript var ndviParams = { min: -0.2, // 修正为缩放后实际范围 max: 1, // MODIS原始值10000对应实际值1 palette: [...] // 保持原色带不变 }; ``` $\textcolor{green}{二、时间序列重构}$ ```javascript // 生成月度合成影像集 var monthlyNDVI = ee.ImageCollection.fromImages( ee.List.sequence(0, 131).map(function(n) { // 2010-2020共132个月 var start = ee.Date('2010-01-01').advance(n, 'month'); var end = start.advance(1, 'month'); return ndviCollection.filterDate(start, end) .select('NDVI').mean() .multiply(0.0001) .set('system:time_start', start.millis()) .set('month', start.format('YYYY-MM')); }) ).filter(ee.Filter.neq('NDVI', null)); // 过滤空数据 // 创建时间序列图表 var chart = ui.Chart.image.series({ imageCollection: monthlyNDVI.select('NDVI'), region: kunyuMountains, reducer: ee.Reducer.mean(), scale: 250, xProperty: 'system:time_start' // 使用标准时间属性 }).setOptions({ title: '2010-2020 Monthly NDVI Variation', hAxis: {title: 'Date', format: 'YYYY-MM'}, // 显示年月格式 vAxis: {title: 'NDVI', viewWindow: {min: 0.3, max: 0.8}}, // 优化显示范围 lineWidth: 2 }); ``` $\textcolor{green}{三、关键改进说明}$ 1. $\text{时间聚合}$:通过遍历132个月创建月平均影像,避免同一月份多个观测值导致的波动 2. $\text{时间戳标准化}$:使用`system:time_start`保证时间轴正确排序 3. $\text{显示优化}$:添加vAxis的viewWindow参数聚焦典型NDVI值域(0.3-0.8) $\textcolor{green}{四、完整优化代码}$ ```javascript // 定义研究区域 var kunyuMountains = ee.Geometry.Polygon([...]); // 坐标需验证 // 加载MOD13Q1数据集 var ndviCollection = ee.ImageCollection('MODIS/006/MOD13Q1') .filterDate('2010-01-01', '2020-12-31') .filterBounds(kunyuMountains); // 年度合成 var annualComposite = ndviCollection.select('NDVI') .mean() .multiply(0.0001); // 可视化参数 var ndviParams = { min: -0.2, max: 1, palette: [...] // 保持原色带 }; // 时间序列处理 var monthlyNDVI = ee.ImageCollection.fromImages( ee.List.sequence(0, 131).map(function(n) { var start = ee.Date('2010-01-01').advance(n, 'month'); var end = start.advance(1, 'month'); return ndviCollection.filterDate(start, end) .select('NDVI').mean() .multiply(0.0001) .set('system:time_start', start.millis()) .set('month', start.format('YYYY-MM')); }) ).filter(ee.Filter.neq('NDVI', null)); // 制图输出 Map.centerObject(kunyuMountains, 9); Map.addLayer(annualComposite.clip(kunyuMountains), ndviParams, 'Annual NDVI'); // 图表生成 var chart = ui.Chart.image.series({ imageCollection: monthlyNDVI.select('NDVI'), region: kunyuMountains, reducer: ee.Reducer.mean(), scale: 250, xProperty: 'system:time_start' }).setOptions({...}); // 保持前文设置 print(chart); // 导出配置保持不变 ``` $\textcolor{green}{五、验证建议}$ 1. 使用`print(ndviCollection.size())`检查数据获取量 2. 添加`print(monthlyNDVI.aggregate_array('month'))`验证月份序列完整性 3. 临时缩小研究区范围进行测试 $\textcolor{red}{特别注意}$:实际运行时需要确保: 1. 多边形坐标准确界定昆嵛山保护区 2. 首次运行需在Code Editor右上角申请MODIS数据集访问权限 3. 导出任务需手动在"Tasks"面板启动并授权 修改后的代码将产生更准确的时空分析结果,月度曲线能真实反映植被季节变化规律。如果需要分析年际变化趋势,可进一步添加线性回归分析模块。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值