D3使用与地图绘制【包含图片生成切片绘制等】(进阶版)(我的成长之路No.12)

文章详细介绍了如何使用D3.js库进行地图的绘制,包括图形生成、渲染、裁剪、填充颜色等功能,以及如何处理本地图片并将其整合到SVG中。此外,还展示了如何进行水系和湖泊的绘制,以及如何处理拖拽和点击事件。代码示例涵盖了从基础地图到进阶功能的多个方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

v1.0版本主要内容包含:图形绘制,渲染,图形生成,虚拟上传,路径裁剪,地图填色,数据填色,数据处理等。

v2.0进阶版本更新:(主要技术栈:D3.js)。

v2.0最新更新时间:2023-06-21。

v2.0主要更新内容:水系、湖泊、内外边界分离等。

进阶:1)、封装公共部分

import * as d3 from 'd3';
import * as turf from '@turf/turf'
import { HttpService } from 'src/app/shared/services/http/http.service';
import PubSub from 'pubsub-js'

export class D3Drawer {


    private pathGenerator: any;
    private projection: any;
    private element: any;
    // private border: any;
    private coordinate: any = null;
    private httpService: HttpService;
    private colorChart: any = null;
    private width: any = null;
    private height: any = null;
    private zoomContains: any = [];
    private contains: any = [];
    // private changeClickElement: any = null;
    private currentTag: any = null;
    private zoom: any = null;


    private currentTags: Map<string, any> = new Map<string, any>();

/**
 * 
 * @param httpService 
 * @param width 图片宽度
 * @param height 图片高度
 * @param id svg dom id
 */
    public constructor(httpService: HttpService, width: number, height: number, id: string) {
        this.httpService = httpService;

        this.width = width;
        this.height = height;
        this.element = d3.select(id)
            .attr("width", this.width)
            .attr("height", this.height);
        // const { event } = require("d3-selection");//动态引入event
        // event.preventDefault();

    }

    public export(exportName: any = 'name') {
        var serializer = new XMLSerializer();
        var source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(this.element.node());
        var image = new Image;
        image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
        var canvas = document.createElement("canvas");
        canvas.width = this.width;
        canvas.height = this.height;
        var context: any = canvas.getContext("2d");
        context.fillStyle = '#fff';
        context.fillRect(0, 0, this.width, this.height);
        image.onload = function () {
            context.drawImage(image, 0, 0);
            var a = document.createElement("a");
            a.download = exportName + ".png";
            a.href = canvas.toDataURL("image/png");
            a.click();
        };
    }
/**
 * 虚拟上传
 * @param pagesType 所属页面
 * @time ***
 * @updataTime 2023-6-16 
 * @writer wy
 */
    public virtualUpload(pagesType: string) {
        var canvas = document.createElement("canvas");
        canvas.width = this.width;
        canvas.height = this.height;
        var context: any = canvas.getContext("2d");
        context.fillStyle = '#fff';
        context.fillRect(0, 0, this.width, this.height);
        var serializer = new XMLSerializer(); // 文档序列化
        var source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(this.element.node()); // 将整个文档序列化为包含XML的字符串
        const imgUrl = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source)
        var image = new Image();
        image.onload = function () {
            context.drawImage(image, 0, 0);
            image.onload = null;
        }
        image.src = imgUrl;

        setTimeout(() => {
            var basePath = canvas.toDataURL("image/png")
            PubSub.publish('Base64' + pagesType, basePath);
        }, 500)

    }

    async exportRadarPicture(exportName: any = 'name', radarPictureUrl: string = '') { // 导出雷达图片
        var canvas = document.createElement("canvas");
        canvas.width = this.width;
        canvas.height = this.height;
        var context: any = canvas.getContext("2d");
        context.fillStyle = '#fff';
        context.fillRect(0, 0, this.width, this.height);

        await this.createBaseMapImage(context);

        var image = new Image;
        image.setAttribute('crossOrigin', 'anonymous');
        image.src = radarPictureUrl
        image.onload = function () {
            context.drawImage(image, 0, 0, canvas.width, canvas.height);
            var a = document.createElement("a");
            a.download = exportName + ".png";
            a.href = canvas.toDataURL("image/png");
            a.click();
        };

    }

    public createBaseMapImage(context) {
        var serializer = new XMLSerializer();
        var image = new Image;
        var source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(this.element.node());
        image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
        image.onload = function () {
            context.drawImage(image, 0, 0);
        };
    }

/**
 * 
 * @param startX 地图开始 水平位置
 * @param startY 地图开始 垂直位置
 * @param width 地图宽度
 * @param height 地图高度
 */
    public initMapInfo(startX: number, startY: number, width: number, height: number, border: any) {
        this.projection = d3.geoMercator().fitExtent( // 墨卡托坐标系以及坐标映射
            [
                [startX, startY],
                [width, height]
            ], border);

        this.pathGenerator = d3.geoPath()
            .projection(this.projection);

        this.zoom = d3.zoom()  //设置监听范围
            .on('start', () => {
                this.element.style('cursor', 'pointer')
            })
            .on('end', () => {
                this.element.style('cursor', 'default')
            })
            .on("zoom", (d: any, i: any) => {
                const t = d.transform;
                this.currentTags.set(this.currentTag, t);
                this.zoomContains.forEach((target: any) => {
                    target.attr("transform", `translate(${t.x}, ${t.y}) scale(${t.k})`); //改变mapContainer的位置及缩放
                });
            });

        this.element.call(this.zoom)
            .on('dblclick.zoom', null);
    }

    public setCoordinate(border: any) {
        // const response: any = await this.httpService.getSync(borderUrl, "");
        // this.border = response;
        this.coordinate = Coordinate.createByBbox(border);
    }

    public async interpolate(values: Array<Station>, interpolateUrl: string, colorChart: ColorChart, clipId: string, tag: string) {
        this.colorChart = colorChart;

        let response: any = await this.httpService.postSync(interpolateUrl, this.toInterpolateValues(values));
        if (response.state != 0) {
            throw new Error(response.message);
        }

        const isobandFeatures = this.isobands(response.data);
        this.drawContour(isobandFeatures, clipId, tag);
        const legendElement = this.colorChart.drawLegend(this.element, this.height, this.contains, 'color');
        this.bindMouseEvent(legendElement, 'color');
    }
/**
 * 差值/色斑图填充
 * @param values 
 * @param elementCode 
 * @param interpolateUrl 
 * @param clipId 
 * @param tag 
 * @time 2023-05-05
 * @updataTime 2023-6-16 
 * @writer wy
 */
    public async interpolatedColorValue(values: Array<Station>, elementCode: string, interpolateUrl: string, clipId: string, tag: string, border: any = null, isAnomaly: boolean = false) { 
        // this.colorChart = colorChart;

        let response: any = await this.httpService.postSync(interpolateUrl, this.toInterpolatedColorValues(values, elementCode));
        if (response.state != 0) {
            throw new Error(response.message);
        }

        let colorArry: any = [];
        let colorValue: any = [];
        let colorLeng: any = null;
        let colorScale: any = response.data.colorScale

        for (let i = 0; i < colorScale.length; i++) {
            colorArry.push(colorScale[i].color)
            colorValue.push(colorScale[i].start, colorScale[i].end)
        }

        colorArry.reverse()
        colorValue = colorValue.filter((value, index, array) => { return array.indexOf(value) == index }).sort((a, b) => b - a)

        colorLeng = response.data.colorScale[0].units
        this.colorChart = new ColorChart(colorArry, colorValue, colorLeng, true, true);
        const isobandFeatures = this.isobands(response.data.data);
        if (border == null) this.drawContour(isobandFeatures, clipId, tag);
        else this.drawContourClipBorder(isobandFeatures, border, clipId, tag, isAnomaly);
        const legendElement = this.colorChart.drawLegend(this.element, this.height, this.contains, 'color');
        this.bindMouseEvent(legendElement, 'color');
    }
/**
 * 产品制作
 * @param border 
 * @param river 
 * @param values 
 * @param elementCode 
 * @param interpolateUrl 
 * @param colorChart 
 * @param clipId 
 * @param tag 
 * @param isAnomaly 距平判断
 * @time ***
 * @updataTime 2023-6-16 
 * @writer wy
 */
    public async interpolateColorValueRain(border: any, values: Array<Station>, elementCode: string, interpolateUrl: string, colorChart: ColorChart, clipId: string, tag: string, isAnomaly: boolean = false) {
        this.colorChart = colorChart;

        let response: any = await this.httpService.postSync(interpolateUrl, this.toInterpolatedColorValues(values, elementCode));
        if (response.state != 0) {
            throw new Error(response.message);
        }

        const isobandFeatures = this.isobands(response.data.data);
        // this.drawContour(isobandFeatures, clipId, tag);
        this.drawContourClipBorder(isobandFeatures, border, clipId, tag, isAnomaly);
        const legendElement = this.colorChart.drawLegend(this.element, this.height, this.contains, 'color');
        this.bindMouseEvent(legendElement, 'color');
    }
/**
 * 自定义地区图例生成
 * @param x 图例水平位置
 * @param y 图例垂直位置
 * @param colorValues 图例内容
 * @param tag 移动属性
 * @param legendStyle 图例样式(分体式:splitType,分体单位式:splitUnitType,垂直式:verticalType,垂直单位式:verticalUnitType)
 * @time ***
 * @updataTime 2023-6-16 
 * @writer wy
 */
    public async interReginColorLegendMethod(x: number, y: number, colorValues: Array<any>, tag: string = 'color', legendStyle: string = 'splitType', unitType: string = 'mm') { // 地区图例 直接生成
        const legendElement = await this.drawReginColorLegend(x, y, colorValues, tag, legendStyle, unitType);
        this.bindMouseEvent(legendElement, tag);
    }
/**
 * 绘制图例
 * @param x 图例水平位置
 * @param y 图例垂直位置
 * @param colorValues 颜色值集
 * @param tag 移动属性
 * @param legendStyle 图例样式
 * @param unitType 单位样式
 * @returns 生成好的图例
 * @time ***
 * @updataTime 2023-6-16 
 * @writer wy
 */
    public drawReginColorLegend(x: number, y: number, colorValues: Array<any>, tag: string , legendStyle: string, unitType: string): any {
        const reginLegendWidth = (this.height / (legendStyle == 'splitType' || legendStyle == 'splitUnitType' ? 4 : 3)) / colorValues.length;
        const legendHeight = this.height - 10 - reginLegendWidth;
        const legendStyleJudeg: boolean =  legendStyle == 'splitType' || legendStyle == 'splitUnitType' ? true : false;
        const legendUnitStyleJudeg: boolean =  legendStyle !== 'splitType' && legendStyle !== 'verticalType' ? true : false;
        let legendElement = this.element.append('g')
            .attr('class', 'legend')
            .selectAll('g')
            .data(colorValues)
            .enter().append("g");

        //绘制文字后方的颜色框或线
        legendElement.append("rect")
            .attr("x", 5 + x)
            .attr("y", (d: any, i: number) => {
                return legendStyleJudeg ? legendHeight - (i * reginLegendWidth) - y : legendHeight - (i * reginLegendWidth) - (y / 1.2);
            })
            .attr("height", legendStyleJudeg ? 20 : reginLegendWidth)
            .attr("width", legendStyleJudeg ? reginLegendWidth : 20)
            .style('stroke-width', 1)
            .style('stroke', '#3e3b3bd9')
            .style("fill", (d: any) => {
                return d.color
            });

        //绘制图例文字
        legendElement.append("text")
            .attr("x", legendStyleJudeg ? reginLegendWidth + 10 + x : reginLegendWidth + x)
            .attr("y", (d: any, i: number) => {
                return legendStyleJudeg ? legendHeight + 15 - (i * reginLegendWidth) - y : legendHeight + reginLegendWidth + 5 - (i * reginLegendWidth) - (y / 1.2);
            })
            .style("text-anchor", "start") //样式对齐
            .text((d: any) => {
                return d.value;
            });

        // let unitElement = this.element.append("g")
        //     .attr("class", "color-unit")
        //     .append('text')
        //     .attr("x", 5 + x)
        //     .attr("y", legendStyleJudeg ? legendHeight + 30 - reginLegendWidth*colorValues.length - y : legendHeight + reginLegendWidth - 5 - (colorValues.length * reginLegendWidth) - (y / 1.2)) 
        //     .style('font-weight', 500)
        //     .style('font-family', 'Arial')
        //     .style('fill', 'black')
        //     .text('单位:' + unitType);
        //     // .text('单位:' + this.unit);

        legendElement.tag = tag;
        // if(legendUnitStyleJudeg) unitElement.t
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值