基于实时矢量切片的要素绘制Demo

文章基于我拆自Geoserver的矢量切片插件中的代码做的一个封装:https://github.com/polixiaohai/mvn-repository。其中有两个比较成熟的封装,有需要的朋友可以自行使用。

maven中仓库配置:

<repository>
    <id>maven-repo-master</id>
    <url>https://raw.github.com/polixiaohai/mvn-repository/master/</url>
</repository>

 包POM:

<!--一个实体转geojson的包-->
<dependency>
    <groupId>com.walkgis.utils</groupId>
    <artifactId>common-geojson</artifactId>
    <version>2.2.0-RELEASE</version>
</dependency>

<!--实体转矢量切片的包-->

<dependency>
    <groupId>com.walkgis.utils</groupId>
    <artifactId>common-gs-vectortile</artifactId>
    <version>2.2.0-RELEASE</version>
</dependency>

工程使用的是SpringBoot,ORM框架使用的是国产ibeetlSQL,如果不喜欢可以自行使用其他的,其他的废话不多说,上代码:

POM文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.com.walkgis.microdraw</groupId>
	<artifactId>walkgis-draw</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>walkgis-draw</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

		<dependency>
			<groupId>com.ibeetl</groupId>
			<artifactId>beetlsql</artifactId>
			<version>2.10.40</version>
		</dependency>

		<dependency>
			<groupId>com.walkgis.utils</groupId>
			<artifactId>common-gs-vectortile</artifactId>
			<version>2.2.0-RELEASE</version>
		</dependency>

		<dependency>
			<groupId>com.walkgis.utils</groupId>
			<artifactId>common-geojson</artifactId>
			<version>2.2.0-RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<version>42.2.4</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

表结构(使用的PG数据库)如下:

    create table if not exists public.t_poi
    (
      id serial not null		constraint t_area_pkey	primary key,
      name varchar(100) not null,
      type integer not null
    );
    select addGeometryColumn('public','t_poi','shape',4326,'Point',2);

    create table if not exists public.t_river
    (
      id serial not null		constraint t_river_pkey	primary key,
      name varchar(100) not null,
      type integer not null
    );
    select addGeometryColumn('public','t_river','shape',4326,'LineString',2);

  create table if not exists public.t_build
  (
    id serial not null		constraint t_build_pkey	primary key,
    name varchar(100) not null,
    type integer not null
  );
  select addGeometryColumn('public','t_build','shape',4326,'Polygon',2);

其中数据源配置和本地测试的时候,SpringBoot跨域设置就不贴出代码来了,可以自行百度。

实体贴出一个来:

public class TBuild implements GeoEntity<Integer> {

    private Integer id;
    private Integer type;
    private String name;
    private Object shape;

    public TBuild() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object getShape() {
        return shape;
    }

    public void setShape(Object shape) {
        this.shape = shape;
    }


}

实体保存的Controller,其他实体保存的Controller可以对照进行实现:

@Controller
@RequestMapping(value = "build")
public class BuildController extends GeoJsonServicesImpl<TBuild, Integer> {
    @Autowired
    @Qualifier("sqlManagerFactoryBeanGIS")
    private SQLManager sqlManagerGIS;

    @RequestMapping(value = "save")
    @ResponseBody
    public Integer save(@RequestParam("feature") String feature) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            Feature fea = mapper.readValue(feature, Feature.class);
            TBuild build = new TBuild();
            BeanUtils.populate(build, fea.getProperties());
            Geometry geometry = geometryConvert.geometryDeserialize(fea.getGeometry());
            if (geometry != null) {
                PGobject pGobject = new PGobject();
                pGobject.setType("Geometry");
                geometry.setSRID(4326);
                pGobject.setValue(WKBWriter.toHex(new WKBWriter(2, true).write(geometry)));
                build.setShape(pGobject);
            }
           return sqlManagerGIS.insert(TBuild.class, build);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return -1;
    }

}

生成矢量切片的Controller,其中有部分对文件做的防止多线程同时操作而作的锁处理,以及利用Java中fork/join来并行下载切片的:

@Controller
@RequestMapping(value = "vectortile")
public class VectorTileControllerImpl extends VectorTileController {
    @Value("${cache.vector-tile-geoserver-path}")
    public String cachePath;

    @Value("${region.split}")
    public String regionSplit;
    @Value("${cache.maxz}")
    private Integer tmaxz;
    @Value("${cache.minz}")
    private Integer tminz;

    public static Map<Integer, Long> info = new ConcurrentHashMap<>();// 总数 原子操作
    public static AtomicInteger successCount = new AtomicInteger();// 总数 原子操作
    public static AtomicInteger zoom = new AtomicInteger();// 总数 原子操作

    private static final ForkJoinPool pool = new ForkJoinPool();

    @Autowired
    @Qualifier("sqlManagerFactoryBeanGIS")
    private SQLManager sqlManagerGIS;

    /**
     * 进来的是XYZ scheme
     *
     * @param layerName
     * @param x
     * @param y
     * @param z
     * @return
     */
    @RequestMapping(value = "vt/{z}/{x}/{y}.mvt", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<InputStreamResource> generateVectorTiles(
            @RequestParam(value = "layerName", defaultValue = "vtdemo") String layerName,
            @RequestParam(value = "CRS") String crs,
            @PathVariable("x") Integer x,
            @PathVariable("y") Integer y,
            @PathVariable("z") Integer z
    ) throws Exception {
        ReadWriteLock lock = new ReentrantReadWriteLock();

        final Lock readLock = lock.readLock();
        final Lock writeLock = lock.writeLock();

        File file = new File(cachePath + File.separator + layerName + File.separator + z + File.separator + x + File.separator + String.format("%d.%s", y, "mvt"));
        if (!file.exists()) {
            //#region 下载内容
            double[] bboxs = new double[]{0, 0, 0, 0};
            if (crs.equalsIgnoreCase("EPSG:4326"))
                bboxs = new GlobalGeodetic("", 256).tileLatLonBounds(x, y, z);
            else if (crs.equalsIgnoreCase("EPSG:3857"))
                bboxs = new GlobalMercator(256).tileLatLonBounds(x, y, z);
            else throw new Exception("不支持的地理坐标系");

            Map<String, List> entityMap = new ConcurrentHashMap<>();
            String sql = "SELECT t.* FROM t_build t  WHERE ST_Intersects (st_setsrid(t.shape,4326),ST_MakeEnvelope(" + bboxs[1] + "," + bboxs[0] + "," + bboxs[3] + "," + bboxs[2] + ",4326))";
            List<TBuild> entityList = sqlManagerGIS.execute(new SQLReady(sql), TBuild.class);
            if (entityList.size() > 0) entityMap.put("t_build", entityList);

            sql = "SELECT t.* FROM t_river t  WHERE ST_Intersects (st_setsrid(t.shape,4326),ST_MakeEnvelope(" + bboxs[1] + "," + bboxs[0] + "," + bboxs[3] + "," + bboxs[2] + ",4326))";
            List<TRiver> tRiverList = sqlManagerGIS.execute(new SQLReady(sql), TRiver.class);
            if (tRiverList.size() > 0) entityMap.put("t_river", tRiverList);

            sql = "SELECT t.* FROM t_poi t  WHERE ST_Intersects (st_setsrid(t.shape,4326),ST_MakeEnvelope(" + bboxs[1] + "," + bboxs[0] + "," + bboxs[3] + "," + bboxs[2] + ",4326))";
            List<TPoi> tPois = sqlManagerGIS.execute(new SQLReady(sql), TPoi.class);
            if (tPois.size() > 0) entityMap.put("t_poi", tPois);

            try {
                if (entityMap.size() <= 0)
                    return downloadFile(readLock, file);

                byte[] res = produceMap(entityMap, bboxs);
                if (res == null || res.length <= 0)
                    return downloadFile(readLock, file);

                try {
                    writeLock.lock();
                    if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
                    FileOutputStream fos = new FileOutputStream(file);
                    fos.write(res, 0, res.length);
                    fos.flush();
                    fos.close();
                    System.out.println("增加:" + file.getAbsolutePath());
                } finally {
                    writeLock.unlock();
                }


            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return downloadFile(readLock, file);
            //endregion
        } else {
            return downloadFile(readLock, file);
        }
    }

    @RequestMapping(value = "buildCache/{layerName}")
    @ResponseBody
    public void buildCach(@PathVariable("layerName") String layerName,
                          @RequestParam(value = "CRS") String crs,
                          @RequestParam(value = "extent", required = false) String extent) {
        String[] str = extent.split(",");
        if (str.length == 4) {
            Double xmin = Double.parseDouble(str[0]);
            Double ymin = Double.parseDouble(str[1]);
            Double xmax = Double.parseDouble(str[2]);
            Double ymax = Double.parseDouble(str[3]);
            Envelope envelope = new Envelope(xmin, xmax, ymin, ymax);
            GlobalMercator mercator = new GlobalMercator(256);
            double[] min = mercator.latLonToMeters(envelope.getMinY(), envelope.getMinX());
            double[] max = mercator.latLonToMeters(envelope.getMaxY(), envelope.getMaxX());

            //#region 计算
            for (int tz = tmaxz; tz > tminz - 1; tz--) {
                int[] tminxy = mercator.metersToTile(min[0], min[1], tz);
                int[] tmaxxy = mercator.metersToTile(max[0], max[1], tz);
                tminxy = new int[]{Math.max(0, tminxy[0]), Math.max(0, tminxy[1])};
                tmaxxy = new int[]{(int) Math.min(Math.pow(2, tz) - 1, tmaxxy[0]), (int) Math.min(Math.pow(2, tz) - 1, tmaxxy[1])};
                info.put(tz, (long) ((tmaxxy[1] - (tminxy[1] - 1)) * (tmaxxy[0] + 1 - tminxy[0])));
                for (int tx = tminxy[0]; tx < tmaxxy[0] + 1; tx++) {
                    pool.execute(new DownloadTask(layerName, crs, tz, tminxy[1], tmaxxy[1], tx));
                }
            }
            //endregion
        }
    }

    @RequestMapping(value = "clearCache/{layerName}", method = RequestMethod.GET)
    @ResponseBody
    public Integer clearCache(@PathVariable("layerName") String layerName,
                              @RequestParam(value = "CRS") String crs,
                              @RequestParam(value = "extent", required = false) String extent) throws Exception {
        if (null == extent) {
            String filePath = cachePath + File.separator + layerName;
            if (new File(filePath).exists()) {
                new Thread(() -> FileUtils.delFolder(filePath)).start();
            }
        } else {
            String[] str = extent.split(",");
            if (str.length == 4) {
                Envelope envelope = new Envelope(Double.parseDouble(str[0]), Double.parseDouble(str[2]), Double.parseDouble(str[1]), Double.parseDouble(str[3]));

                double[] min = new double[0], max = new double[0];
                GlobalMercator mercator = null;
                GlobalGeodetic geodetic = null;

                if (crs.equalsIgnoreCase("EPSG:4326")) {
                    geodetic = new GlobalGeodetic("", 256);
                } else if (crs.equalsIgnoreCase("EPSG:3857")) {
                    mercator = new GlobalMercator(256);
                    min = mercator.latLonToMeters(envelope.getMinY(), envelope.getMinX());
                    max = mercator.latLonToMeters(envelope.getMaxY(), envelope.getMaxX());
                } else throw new Exception("不支持的地理坐标系");


                //#region 计算
                for (int tz = tmaxz; tz > tminz - 1; tz--) {
                    int[] tminxy = new int[0], tmaxxy = new int[0];
                    if ((crs.equalsIgnoreCase("EPSG:3857"))) {
                        tminxy = mercator.metersToTile(min[0], min[1], tz);
                        tmaxxy = mercator.metersToTile(max[0], max[1], tz);
                    } else if (crs.equalsIgnoreCase("EPSG:4326")) {
                        tminxy = geodetic.lonlatToTile(envelope.getMinX(), envelope.getMinY(), tz);
                        tmaxxy = geodetic.lonlatToTile(envelope.getMaxX(), envelope.getMaxY(), tz);
                    }

                    tminxy = new int[]{Math.max(0, tminxy[0]), Math.max(0, tminxy[1])};
                    tmaxxy = new int[]{(int) Math.min(Math.pow(2, tz) - 1, tmaxxy[0]), (int) Math.min(Math.pow(2, tz) - 1, tmaxxy[1])};

                    for (int tx = tminxy[0]; tx < tmaxxy[0] + 1; tx++) {
                        for (int ty = tmaxxy[1]; ty > tminxy[1] - 1; ty--) {

                            File file = new File(cachePath + File.separator + layerName + File.separator + tz + File.separator + tx + File.separator + ty + ".mvt");
                            if (file.exists())
                                file.delete();
                            System.out.println("删除:" + file.getAbsolutePath());
                        }
                    }
                }
                return 1;
                //endregion
            }
        }
        return -1;
    }

    public ResponseEntity<InputStreamResource> downloadFile(Lock readLock, File filePath) {
        if (filePath.exists()) {
            try {
                readLock.lock();
                FileSystemResource file = new FileSystemResource(filePath);

                return ResponseEntity.ok().contentLength(file.contentLength())
                        .contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
                        .body(new InputStreamResource(file.getInputStream()));
            } catch (IOException e) {
                return ResponseEntity.noContent().build();
            } finally {
                readLock.unlock();
            }
        } else {
            return ResponseEntity.noContent().build();
        }
    }

    private class DownloadTask extends RecursiveTask<Void> {
        private static final long serialVersionUID = 1L;

        private static final int THRESHOLD = 1000;
        private String layerName;
        private String crs;
        private int tz;
        private int start;
        private int end;
        private int tx;

        public DownloadTask(String layerName, String crs, int tz, int start, int end, int tx) {
            this.layerName = layerName;
            this.crs = crs;
            this.tz = tz;
            this.start = start;
            this.end = end;
            this.tx = tx;
        }

        @Override
        protected Void compute() {
            if (end - (start - 1) <= THRESHOLD) {
                for (int ty = end; ty > start - 1; ty--) {
                    try {
                        generateVectorTiles(layerName, crs, tx, ty, tz);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    successCount.getAndIncrement();
                    zoom = new AtomicInteger(tz);
                }
            }
            int m = (start - 1) + (end - (start - 1)) / 2;
            DownloadTask task1 = new DownloadTask(layerName, crs, tz, start, m, tx);
            DownloadTask task2 = new DownloadTask(layerName, crs, tz, m, end, tx);
            invokeAll(task1, task2);
            return null;
        }
    }
}

SpringBoot(application-local.yml,这里还需要配置一个application.yml指定那个profile为active的)配置文件部分 :

server:
  port: 8084
  tomcat:
    uri-encoding: UTF-8
  servlet:
    context-path: /${spring.application.name}
spring:
  profiles: local
  datasource:
    gis:
      url: jdbc:postgresql://localhost:5432/gis
      username: postgres
      password: postgres
      driver-class-name: org.postgresql.Driver
      sql-script-encoding: utf-8
      type: com.zaxxer.hikari.HikariDataSource
      hikari:
        connection-timeout: 30000
        idle-timeout: 60000
        max-lifetime: 1800000
        maximum-pool-size: 60
        minimum-idle: 0
  application:
    name: walkgis-draw

beetlsql:
  ds:
    gis:
      sqlPath: /sql
      basePackage: cn.com.walkgis.microdraw.walkgisdraw.dao
      nameConversion: org.beetl.sql.core.UnderlinedNameConversion
      daoSuffix: Dao
      dbStyle: org.beetl.sql.core.db.PostgresStyle
  mutiple:
    datasource: gis
beetl-beetlsql: dev=false


# 存放缓存文件的地址
cache:
  vector-tile-geoserver-path: E:\Data\tiles\vt-geoserver
  maxz: 18
  minz: 1

前端调用页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--bootstrap-->
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!--jqueryUI-->
    <link href="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet">
    <link href="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.theme.min.css" rel="stylesheet">
    <link href="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.theme.min.css" rel="stylesheet">
    <!--openlayers-->
    <link href="https://cdn.bootcss.com/openlayers/4.6.5/ol-debug.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script src="https://cdn.bootcss.com/jqueryui/1.12.1/jquery-ui.min.js"></script>

    <script src="https://cdn.bootcss.com/openlayers/4.6.5/ol-debug.js"></script>
    <style>
        html, body {
            width: 100%;
            height: 100%;
        }

        #map {
            width: 100%;
            height: 100%;
            border: 1px solid red;
        }

        #tools {
            position: fixed;
            left: 20px;
            top: 40px;
            z-index: 99999;
            background: #fff;
        }

        #tools select {
            width: 150px;
        }
    </style>
</head>
<body>
<div id="tools">
    <select name="typeSelect" id="typeSelect">
        <option value="Point" data="poi" selected="selected">t_poi</option>
        <option value="LineString" data="river">t_river</option>
        <option value="Polygon" data="build">t_build</option>
    </select>
</div>
<div id="map"></div>
<div id="dialog" title="保存对话框">
    <form>
        <div class="form-group">
            <label for="inputName">名称:</label>
            <input type="email" class="form-control" name="name" id="inputName" placeholder="名称">
        </div>
        <div class="form-group">
            <label for="selectType">类型:</label>
            <select class="form-control" name="type" id="selectType" placeholder="类型">
                <option value="1" selected="selected">点</option>
                <option value="2">线</option>
                <option value="3">面</option>
            </select>
        </div>
    </form>
</div>
<script type="text/javascript">
    $(function () {
        var map, draw, snap, baseLayer, baseSource, tempLayer;

        baseSource = new ol.source.VectorTile({
            format: new ol.format.MVT(),
            url: 'http://localhost:8084/walkgis-draw/vectortile/vt/{z}/{x}/{-y}.mvt?CRS=EPSG:4326',
            projection: "EPSG:4326",
            extent: ol.proj.get("EPSG:4326").getExtent(),
            tileSize: 256,
            maxZoom: 21,
            minZoom: 0,
            wrapX: true
        });
        baseLayer = new ol.layer.VectorTile({
            renderMode: "image",
            preload: 12,
            source: baseSource,
            style: new ol.style.Style({
                fill: new ol.style.Fill({
                    color: 'rgba(255, 0, 0, 0.2)'
                }),
                stroke: new ol.style.Stroke({
                    color: '#ff0000',
                    width: 2
                }),
                image: new ol.style.Circle({
                    radius: 7,
                    fill: new ol.style.Fill({
                        color: '#ff0000'
                    })
                })
            })
        })
        tempLayer = new ol.layer.Vector({
            source: new ol.source.Vector()
        })

        function addInteractions() {
            draw = new ol.interaction.Draw({
                source: tempLayer.getSource(),
                type: typeSelect.value,
                style: new ol.style.Style({
                    fill: new ol.style.Fill({
                        color: 'rgba(255, 255, 255, 0.2)'
                    }),
                    stroke: new ol.style.Stroke({
                        color: '#ffcc33',
                        width: 2
                    }),
                    image: new ol.style.Circle({
                        radius: 7,
                        fill: new ol.style.Fill({
                            color: '#ffcc33'
                        })
                    })
                })
            });
            draw.on('drawend', function (target) {
                $("#dialog").dialog({
                    title: '保存对话框',
                    dialogClass: "no-close",
                    width: 600,
                    height: 300,
                    modal: true,
                    buttons: {
                        '确  定': function () {
                            tempLayer.getSource().clear();
                            var attrs = $(this).find('form').serializeArray();
                            $(this).find('form')[0].reset();
                            attrs.forEach(function (item) {
                                target.feature.set(item.name, item.value);
                            })

                            var route = $("#typeSelect option:selected").attr("data")
                            $.ajax({
                                url: 'http://localhost:8084/walkgis-draw/' + route + "/save",
                                type: "GET",
                                data: {
                                    feature: new ol.format.GeoJSON().writeFeature(target.feature)
                                }
                            }).done(function (re) {
                                if (re > 0) {
                                    var extent = target.feature.getGeometry().getExtent().join(',')
                                    $.ajax({
                                        url: 'http://localhost:8084/walkgis-draw/vectortile/clearCache/vtdemo?CRS=EPSG:4326',
                                        type: "GET",
                                        data: {
                                            extent: extent
                                        }
                                    }).done(function (re) {
                                        baseLayer.getSource().clear();
                                        baseLayer.getSource().dispatchEvent("change");
                                    })
                                }
                            })
                            $(this).dialog('destroy');
                        },
                        '取  消': function () {
                            tempLayer.getSource().clear();
                            $(this).dialog('destroy');
                        }
                    }
                });
            })
            snap = new ol.interaction.Snap({
                source: tempLayer.getSource()
            });

            map.addInteraction(draw);
            map.addInteraction(snap);
        }

        $("#typeSelect").change(function () {
            map.removeInteraction(draw);
            map.removeInteraction(snap);
            addInteractions();
        });

        map = new ol.Map({
            target: "map",
            layers: [
                new ol.layer.Tile({
                    source: new ol.source.OSM(),
                    projection: "EPSG:4326"
                }),
                baseLayer,
                new ol.layer.Tile({
                    source: new ol.source.TileDebug({
                        projection: "EPSG:4326",
                        tileGrid: ol.tilegrid.createXYZ({
                            tileSize: [256, 256],
                            minZoom: 0,
                            maxZoom: 18,
                            extent: ol.proj.get("EPSG:4326").getExtent()
                        }),
                        wrapX: true
                    }),
                    projection: 'EPSG:4326'
                }),
                tempLayer
            ],
            view: new ol.View({
                center: [100, 25],
                projection: "EPSG:4326",
                zoom: 10
            }),
            projection: "EPSG:4326",
        })

        addInteractions();
    })

</script>
</body>
</html>

效果如下:矢量切片1

矢量切片
矢量切片
矢量切片2
矢量切片

工程下载地址:https://download.csdn.net/download/polixiaohai/10873787

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值