commit 7964ce15696b364c29cb5f628bcc40e0596e2b08
Author: ZZX9599 <536509593@qq.com>
Date: Mon Sep 8 17:06:22 2025 +0800
核心SDK提交
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..63e9001
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..fcc773f
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..a2f424d
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "associatedIndex": 3
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1743471024061
+
+
+ 1743471024061
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1743472791450
+
+
+
+ 1743472791450
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3ead853
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,107 @@
+
+
+ 4.0.0
+
+
+ com.yjdsj
+ geographysdk
+ 1.0.0
+
+
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.3.3.RELEASE
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.5.1
+
+
+
+
+ com.baomidou
+ mybatis-plus-extension
+ 3.5.1
+
+
+
+
+ com.baomidou
+ mybatis-plus-generator
+ 3.5.1
+
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+ 2.3
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+
+
+
+ org.xerial
+ sqlite-jdbc
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.1.3
+
+
+
+
+ com.github.xiaoymin
+ knife4j-spring-boot-starter
+ 2.0.7
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.7.8
+
+
+
+
+ geographysdk
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/src/main/java/com/yjdsj/SdkApp.java b/src/main/java/com/yjdsj/SdkApp.java
new file mode 100644
index 0000000..d1899da
--- /dev/null
+++ b/src/main/java/com/yjdsj/SdkApp.java
@@ -0,0 +1,11 @@
+package com.yjdsj;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SdkApp {
+ public static void main(String[] args) {
+ SpringApplication.run(SdkApp.class, args);
+ }
+}
diff --git a/src/main/java/com/yjdsj/clt/controller/CltController.java b/src/main/java/com/yjdsj/clt/controller/CltController.java
new file mode 100644
index 0000000..d9fbd4e
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/controller/CltController.java
@@ -0,0 +1,66 @@
+package com.yjdsj.clt.controller;
+
+
+import com.yjdsj.clt.domain.CltTiles;
+import com.yjdsj.clt.service.CltService;
+import com.yjdsj.common.map.SourceMap;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+@Slf4j
+@RestController
+@Api(tags = "倾斜模型资源")
+@RequestMapping("/data/clt")
+public class CltController {
+
+ @Resource
+ private CltService cltService;
+ @Resource
+ private SourceMap sourceMap;
+
+ @ApiOperation(value = "获取倾斜模型资源列表")
+ @GetMapping("/list")
+ public Object list() {
+ return sourceMap.getCltSourceList();
+ }
+
+ @ApiOperation(value = "获取倾斜模型资源的详情")
+ @GetMapping("/detail/{mark}")
+ public Object detail(@PathVariable @ApiParam(value = "资源标识") String mark) {
+ return cltService.detail(mark);
+ }
+
+ @GetMapping("/{mark}/{path:.*}")
+ public ResponseEntity> getData(@PathVariable @ApiParam(value = "资源标识") String mark, @PathVariable String path, HttpServletRequest request) {
+ try {
+ // 查询对应的 CltTiles 数据
+ CltTiles cltTiles = cltService.getTileData(mark, path);
+ if (cltTiles == null) {
+ return ResponseEntity.notFound().build();
+ }
+
+ // 获取是否需要压缩
+ boolean isZip = cltService.checkIsZip(mark);
+
+ // 处理解压数据并返回
+ Object responseData = cltService.handleDecompressedData(cltTiles, isZip);
+ return ResponseEntity.ok(responseData);
+
+ } catch (IOException e) {
+ log.error("处理瓦片数据失败", e);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/clt/domain/CltInfos.java b/src/main/java/com/yjdsj/clt/domain/CltInfos.java
new file mode 100644
index 0000000..9d90218
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/domain/CltInfos.java
@@ -0,0 +1,8 @@
+package com.yjdsj.clt.domain;
+
+import lombok.Data;
+
+@Data
+public class CltInfos {
+ private String params;
+}
diff --git a/src/main/java/com/yjdsj/clt/domain/CltTiles.java b/src/main/java/com/yjdsj/clt/domain/CltTiles.java
new file mode 100644
index 0000000..4564793
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/domain/CltTiles.java
@@ -0,0 +1,10 @@
+package com.yjdsj.clt.domain;
+
+import lombok.Data;
+@Data
+public class CltTiles {
+ private String md5;
+ private String path;
+ private byte[] tile;
+ private String type;
+}
diff --git a/src/main/java/com/yjdsj/clt/mapper/CltInfosMapper.java b/src/main/java/com/yjdsj/clt/mapper/CltInfosMapper.java
new file mode 100644
index 0000000..ff2c757
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/mapper/CltInfosMapper.java
@@ -0,0 +1,8 @@
+package com.yjdsj.clt.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CltInfosMapper {
+ String selectParams();
+}
diff --git a/src/main/java/com/yjdsj/clt/mapper/CltTilesMapper.java b/src/main/java/com/yjdsj/clt/mapper/CltTilesMapper.java
new file mode 100644
index 0000000..bcadd73
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/mapper/CltTilesMapper.java
@@ -0,0 +1,9 @@
+package com.yjdsj.clt.mapper;
+
+import com.yjdsj.clt.domain.CltTiles;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CltTilesMapper {
+ CltTiles selectTileByMd5(String md5);
+}
diff --git a/src/main/java/com/yjdsj/clt/service/CltService.java b/src/main/java/com/yjdsj/clt/service/CltService.java
new file mode 100644
index 0000000..9d067f9
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/service/CltService.java
@@ -0,0 +1,120 @@
+package com.yjdsj.clt.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.yjdsj.clt.domain.CltTiles;
+import com.yjdsj.clt.mapper.CltInfosMapper;
+import com.yjdsj.clt.mapper.CltTilesMapper;
+import com.yjdsj.clt.vo.CltDetailVo;
+import com.yjdsj.common.ds.DynamicDataSourceContextHolder;
+import com.yjdsj.common.map.SourceMap;
+import com.yjdsj.common.utils.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+@Slf4j
+@Service
+public class CltService {
+
+ @Resource
+ private CltInfosMapper cltInfosMapper;
+
+ @Resource
+ private CltTilesMapper cltTilesMapper;
+
+ @Resource
+ private SourceMap sourceMap;
+
+ // 是否需要压缩缓存
+ private final ConcurrentMap zipCache = new ConcurrentHashMap<>();
+
+ public Map getCltSourceList() {
+ return sourceMap.getCltSourceList();
+ }
+
+ public CltDetailVo detail(String mark) {
+ CltDetailVo result = new CltDetailVo();
+ try {
+ // 读取 JSON 文件
+ String json = DecompressionUtil.decompressAndConvertToString(getTileData(mark, "tileset.json").getTile());
+ // 读取JSON字符串为根节点
+ ObjectNode rootNode = (ObjectNode) JacksonUtil.mapper.readTree(json);
+ // 逐层定位到 root 节点 → 再定位到 transform 节点
+ ObjectNode rootChildNode = (ObjectNode) rootNode.get("root");
+ ArrayNode transformArrayNode = (ArrayNode) rootChildNode.get("transform");
+ // 将 ArrayNode 转换为 double[] 数组
+ double[] transform = new double[transformArrayNode.size()];
+ for (int i = 0; i < transformArrayNode.size(); i++) {
+ transform[i] = transformArrayNode.get(i).asDouble();
+ }
+ PositionUtil.BlhResult blhResult = PositionUtil.xyz2Blh(transform[12], transform[13], transform[14]);
+ // 设置 URL
+ result.setUrl(String.format("/data/clt/%s/tileset.json", mark));
+ // 设置 3D 旋转参数
+ CltDetailVo.Orientation orientation = new CltDetailVo.Orientation(0, 0, 0);
+ result.setOrientation(orientation);
+ // 设置位置
+ CltDetailVo.Position position = new CltDetailVo.Position(
+ blhResult.lon,
+ blhResult.lat,
+ blhResult.alt
+ );
+ result.setPosition(position);
+ result.setFileType("tileset");
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public CltTiles getTileData(String mark, String path) {
+ // 设置数据源
+ DynamicDataSourceContextHolder.setDataSourceKey(mark);
+ try {
+ // 查询对应的 CltTiles 数据
+ return cltTilesMapper.selectTileByMd5(MD5HashUtil.toMD5(path));
+ } finally {
+ // 清理数据源设置
+ DynamicDataSourceContextHolder.clearDataSourceKey();
+ }
+ }
+
+ public boolean checkIsZip(String mark) {
+ // 设置数据源
+ DynamicDataSourceContextHolder.setDataSourceKey(mark);
+ String paramsJson = cltInfosMapper.selectParams();
+ try {
+ JsonNode rootNode = JacksonUtil.mapper.readTree(paramsJson);
+ return rootNode.path("zip").asBoolean(false);
+ } catch (IOException e) {
+ return false;
+ } finally {
+ // 清理数据源设置
+ DynamicDataSourceContextHolder.clearDataSourceKey();
+ }
+ }
+
+ public Object handleDecompressedData(CltTiles cltTiles, boolean isZip) throws IOException {
+ if (isZip) {
+ if ("tileset.json".equals(cltTiles.getPath())) {
+ // 解压缩并返回字符串
+ return DecompressionUtil.decompressAndConvertToString(cltTiles.getTile());
+ } else {
+ // 解压缩并返回字节数组
+ return GzipDecompressorUtil.decompress(cltTiles.getTile());
+ }
+ }
+ // 不需要解压则直接返回对应的数据
+ return cltTiles.getTile();
+ }
+}
diff --git a/src/main/java/com/yjdsj/clt/vo/CltDetailVo.java b/src/main/java/com/yjdsj/clt/vo/CltDetailVo.java
new file mode 100644
index 0000000..ac9349c
--- /dev/null
+++ b/src/main/java/com/yjdsj/clt/vo/CltDetailVo.java
@@ -0,0 +1,28 @@
+package com.yjdsj.clt.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+public class CltDetailVo {
+ private String url;
+ private Orientation orientation;
+ private Position position;
+ private String fileType;
+
+ @Data
+ @AllArgsConstructor
+ public static class Orientation {
+ private double heading;
+ private double roll;
+ private double pitch;
+ }
+
+ @Data
+ @AllArgsConstructor
+ public static class Position {
+ private double lon;
+ private double lat;
+ private double alt;
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/config/CustomCorsFilter.java b/src/main/java/com/yjdsj/common/config/CustomCorsFilter.java
new file mode 100644
index 0000000..53edafd
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/config/CustomCorsFilter.java
@@ -0,0 +1,23 @@
+package com.yjdsj.common.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+@Configuration
+public class CustomCorsFilter {
+
+ @Bean
+ public CorsFilter corsFilter() {
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowCredentials(true);
+ config.addAllowedOrigin("*");
+ config.addAllowedHeader("*");
+ config.addAllowedMethod("*");
+ source.registerCorsConfiguration("/**", config);
+ return new CorsFilter(source);
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/config/ServerConfig.java b/src/main/java/com/yjdsj/common/config/ServerConfig.java
new file mode 100644
index 0000000..903bdde
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/config/ServerConfig.java
@@ -0,0 +1,15 @@
+package com.yjdsj.common.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+public class ServerConfig {
+ @Value("${server.port}")
+ private int port;
+
+ @Value("${server.host}")
+ private String host;
+}
diff --git a/src/main/java/com/yjdsj/common/config/SwaggerConfig.java b/src/main/java/com/yjdsj/common/config/SwaggerConfig.java
new file mode 100644
index 0000000..cabc84e
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/config/SwaggerConfig.java
@@ -0,0 +1,34 @@
+package com.yjdsj.common.config;
+
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+
+@Configuration
+@EnableSwagger2WebMvc
+public class SwaggerConfig {
+ @Bean
+ public Docket webApiConfig() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("对接API")
+ .description("对接相关接口")
+ .version("1.0")
+ .build();
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/config/WebConfig.java b/src/main/java/com/yjdsj/common/config/WebConfig.java
new file mode 100644
index 0000000..ba4a4c5
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/config/WebConfig.java
@@ -0,0 +1,18 @@
+package com.yjdsj.common.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**").allowedOrigins("*") // 允许所有来源
+ .allowedMethods("*") // 允许所有方法(GET, POST, PUT, DELETE 等)
+ .allowedHeaders("*") // 允许所有头
+ .allowCredentials(true) // 允许发送凭证(cookies 等)
+ .maxAge(3600); // 预检请求的缓存时间(单位:秒)
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/controller/SourceMapController.java b/src/main/java/com/yjdsj/common/controller/SourceMapController.java
new file mode 100644
index 0000000..b7819fd
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/controller/SourceMapController.java
@@ -0,0 +1,33 @@
+package com.yjdsj.common.controller;
+
+import com.yjdsj.common.map.SourceMap;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@Api(tags = "资源管理")
+@Slf4j
+@RestController
+@RequestMapping("/sourceMap")
+public class SourceMapController {
+
+ @Resource
+ private SourceMap sourceMap;
+
+ // 添加资源映射关系
+ @PostMapping("/add")
+ @ApiOperation(value = "添加资源映射关系")
+ public Object addSourceMapping(@RequestParam String filePath) {
+ return sourceMap.addSourceMapping(filePath);
+ }
+
+ // 移除资源映射关系
+ @DeleteMapping("/remove")
+ @ApiOperation(value = "移除资源映射关系")
+ public void removeSourceMapping(@RequestParam String filePath) {
+ sourceMap.removeSourceMapping(filePath);
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/ds/DataSourceConfig.java b/src/main/java/com/yjdsj/common/ds/DataSourceConfig.java
new file mode 100644
index 0000000..2c9270a
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/ds/DataSourceConfig.java
@@ -0,0 +1,38 @@
+package com.yjdsj.common.ds;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+@Configuration
+public class DataSourceConfig {
+
+ @Resource
+ private DynamicDataSourceManager dynamicDataSourceManager;
+
+ @Bean(name = "dynamicDataSource")
+ public DataSource dynamicDataSource() {
+ DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(dynamicDataSourceManager);
+ return dynamicRoutingDataSource;
+ }
+
+ @Bean
+ public SqlSessionFactory sqlSessionFactory(@Autowired DataSource dynamicDataSource) throws Exception {
+ SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
+ sessionFactoryBean.setDataSource(dynamicDataSource);
+ sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
+ return sessionFactoryBean.getObject();
+ }
+
+ @Bean
+ public SqlSessionTemplate sqlSessionTemplate(@Autowired SqlSessionFactory sqlSessionFactory) {
+ return new SqlSessionTemplate(sqlSessionFactory);
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/ds/DynamicDataSourceContextHolder.java b/src/main/java/com/yjdsj/common/ds/DynamicDataSourceContextHolder.java
new file mode 100644
index 0000000..95e25dc
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/ds/DynamicDataSourceContextHolder.java
@@ -0,0 +1,18 @@
+package com.yjdsj.common.ds;
+
+public class DynamicDataSourceContextHolder {
+
+ private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
+
+ public static void setDataSourceKey(String key) {
+ CONTEXT_HOLDER.set(key);
+ }
+
+ public static String getDataSourceKey() {
+ return CONTEXT_HOLDER.get();
+ }
+
+ public static void clearDataSourceKey() {
+ CONTEXT_HOLDER.remove();
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/ds/DynamicDataSourceManager.java b/src/main/java/com/yjdsj/common/ds/DynamicDataSourceManager.java
new file mode 100644
index 0000000..20e3c36
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/ds/DynamicDataSourceManager.java
@@ -0,0 +1,46 @@
+package com.yjdsj.common.ds;
+
+import com.yjdsj.common.map.SourceMap;
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class DynamicDataSourceManager {
+
+ private final Map dataSourceCache = new HashMap<>();
+ private final SourceMap sourceMap;
+
+ @Autowired
+ public DynamicDataSourceManager(SourceMap sourceMap) {
+ this.sourceMap = sourceMap;
+ }
+
+ public DataSource getDataSource(String dbName) {
+ // 检查缓存中是否已有数据源
+ if (dataSourceCache.containsKey(dbName)) {
+ return dataSourceCache.get(dbName);
+ } else {
+ // 动态创建数据源
+ String dbPath = sourceMap.getSourceMapping(dbName);
+ if (dbPath != null) {
+ DataSource dataSource = createDataSource(dbPath);
+ dataSourceCache.put(dbName, dataSource);
+ return dataSource;
+ } else {
+ throw new IllegalArgumentException("Data source not found for name: " + dbName);
+ }
+ }
+ }
+
+ private DataSource createDataSource(String dbPath) {
+ HikariDataSource dataSource = new HikariDataSource();
+ dataSource.setJdbcUrl("jdbc:sqlite:" + dbPath);
+ dataSource.setDriverClassName("org.sqlite.JDBC");
+ return dataSource;
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/ds/DynamicRoutingDataSource.java b/src/main/java/com/yjdsj/common/ds/DynamicRoutingDataSource.java
new file mode 100644
index 0000000..cb50ac4
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/ds/DynamicRoutingDataSource.java
@@ -0,0 +1,28 @@
+package com.yjdsj.common.ds;
+
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+
+public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
+
+ private final DynamicDataSourceManager dataSourceManager;
+
+ public DynamicRoutingDataSource(DynamicDataSourceManager dataSourceManager) {
+ this.dataSourceManager = dataSourceManager;
+ this.setTargetDataSources(new HashMap<>());
+ this.afterPropertiesSet();
+ }
+
+ @Override
+ protected Object determineCurrentLookupKey() {
+ return DynamicDataSourceContextHolder.getDataSourceKey();
+ }
+
+ @Override
+ protected DataSource determineTargetDataSource() {
+ String dataSourceKey = (String) determineCurrentLookupKey();
+ return dataSourceManager.getDataSource(dataSourceKey);
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/generator/CodeGenerator.java b/src/main/java/com/yjdsj/common/generator/CodeGenerator.java
new file mode 100644
index 0000000..75281d3
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/generator/CodeGenerator.java
@@ -0,0 +1,61 @@
+package com.yjdsj.common.generator;
+
+import com.baomidou.mybatisplus.generator.FastAutoGenerator;
+import com.baomidou.mybatisplus.generator.config.OutputFile;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+
+import java.util.Collections;
+
+/**
+ *
+ * @date 2024/6/26 14:42
+ */
+public class CodeGenerator {
+
+ private static String databasePath = "D:\\DJ\\source\\广西提供poi.poi"; // SQLite 数据库文件路径
+ private static String author = "周志雄";
+
+ public static void main(String[] args) {
+ Generation(databasePath, "tbl_pois"); // 生成指定表的代码,这里以 "users" 表为例
+ }
+
+ public static void Generation(String databasePath, String... tableName) {
+ FastAutoGenerator.create("jdbc:sqlite:" + databasePath, "", "")
+ .globalConfig(builder -> {
+ builder.author(author) // 设置作者名
+ .enableSwagger() // 启用Swagger
+ .outputDir(System.getProperty("user.dir") + "/src/main/java"); // 指定输出目录
+ })
+ .packageConfig(builder -> {
+ builder.entity("domain") // 实体类包名
+ .parent("com.xzjm.poi") // 父包名
+ .controller("controller") // 控制层包名
+ .mapper("mapper") // mapper 层包名
+ .service("service") // service 层包名
+ .serviceImpl("service.impl") // service 实现类包名
+ // 自定义 mapper.xml 文件输出目录
+ .pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + "/src/main/resources/mapper"));
+ })
+ .strategyConfig(builder -> {
+ builder.addInclude(tableName) // 设置要生成的表名
+ .addTablePrefix("t_") // 设置表前缀过滤(如果没有,可以去掉)
+ .entityBuilder()
+ .enableLombok() // 启用 Lombok
+ .naming(NamingStrategy.underline_to_camel) // 表名映射为实体类驼峰命名
+ .columnNaming(NamingStrategy.underline_to_camel) // 字段映射为驼峰命名
+ .mapperBuilder()
+ .enableMapperAnnotation() // 启用 @Mapper 注解
+ .enableBaseResultMap() // 启用 BaseResultMap 生成
+ .enableBaseColumnList() // 启用 BaseColumnList 生成
+ .formatMapperFileName("%sMapper") // 格式化 Mapper 文件名称
+ .formatXmlFileName("%sMapper") // 格式化 XML 文件名称
+ .serviceBuilder()
+ .formatServiceFileName("%sService") // 格式化 Service 接口文件名称
+ .formatServiceImplFileName("%sServiceImpl") // 格式化 Service 实现类文件名称
+ .controllerBuilder()
+ .enableRestStyle() // 启用 Restful 风格
+ .formatFileName("%sController") // 格式化 Controller 文件名称
+ .enableHyphenStyle(); // 启用驼峰转连字符
+ }).execute();
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/handler/BlobTypeHandler.java b/src/main/java/com/yjdsj/common/handler/BlobTypeHandler.java
new file mode 100644
index 0000000..a8e2603
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/handler/BlobTypeHandler.java
@@ -0,0 +1,36 @@
+package com.yjdsj.common.handler;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ *
+ * @date 2024/8/20 11:46
+ */
+public class BlobTypeHandler extends BaseTypeHandler {
+
+ @Override
+ public void setNonNullParameter(PreparedStatement ps, int i, byte[] parameter, JdbcType jdbcType) throws SQLException {
+ ps.setBytes(i, parameter);
+ }
+
+ @Override
+ public byte[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
+ return rs.getBytes(columnName);
+ }
+
+ @Override
+ public byte[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+ return rs.getBytes(columnIndex);
+ }
+
+ @Override
+ public byte[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+ return cs.getBytes(columnIndex);
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/map/MapDto.java b/src/main/java/com/yjdsj/common/map/MapDto.java
new file mode 100644
index 0000000..397b3bf
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/map/MapDto.java
@@ -0,0 +1,9 @@
+package com.yjdsj.common.map;
+
+import lombok.Data;
+
+@Data
+public class MapDto {
+ private String filePath;
+ private Integer userId;
+}
diff --git a/src/main/java/com/yjdsj/common/map/SourceMap.java b/src/main/java/com/yjdsj/common/map/SourceMap.java
new file mode 100644
index 0000000..2f1aa23
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/map/SourceMap.java
@@ -0,0 +1,91 @@
+package com.yjdsj.common.map;
+
+import com.yjdsj.common.utils.MD5HashUtil;
+import org.springframework.stereotype.Component;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class SourceMap {
+
+ private Map sourceMap = new ConcurrentHashMap<>();
+
+ // 添加资源映射关系
+ public String addSourceMapping(String filePath) {
+ sourceMap.put(MD5HashUtil.toMD5(filePath), filePath);
+ return MD5HashUtil.toMD5(filePath);
+ }
+
+ // 移除资源映射关系
+ public void removeSourceMapping(String filePath) {
+ sourceMap.remove(MD5HashUtil.toMD5(filePath));
+ }
+
+ // 获取资源映射关系
+ public String getSourceMapping(String mark) {
+ return sourceMap.get(mark);
+ }
+
+ // 获取倾斜模型资源列表
+ public Map getCltSourceList() {
+ Map sourceList = new HashMap<>();
+ for (Map.Entry entry : sourceMap.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (value.endsWith(".clt")) {
+ String fileName = getFileNameWithExtension(value);
+ sourceList.put(key, fileName);
+ }
+ }
+ return sourceList;
+ }
+
+ // 获取图层资源列表
+ public Map getMBTilesSourceList() {
+ Map sourceList = new HashMap<>();
+ for (Map.Entry entry : sourceMap.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (value.endsWith(".mbtiles")) {
+ String fileName = getFileNameWithExtension(value);
+ sourceList.put(key, fileName);
+ }
+ }
+ return sourceList;
+ }
+
+ // 获取地形资源列表
+ public Map getPakSourceList() {
+ Map sourceList = new HashMap<>();
+ for (Map.Entry entry : sourceMap.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (value.endsWith(".pak")) {
+ String fileName = getFileNameWithExtension(value);
+ sourceList.put(key, fileName);
+ }
+ }
+ return sourceList;
+ }
+
+ /**
+ * 获取文件名和后缀
+ */
+ public static String getFileNameWithExtension(String filePath) {
+ Path path = Paths.get(filePath); // 获取路径
+ return path.getFileName().toString(); // 返回文件名和后缀
+ }
+
+ /**
+ * 遍历数据并打印内容、无参
+ */
+ public void printSourceMap() {
+ for (Map.Entry entry : sourceMap.entrySet()) {
+ System.out.println("MD5 Key: " + entry.getKey() + ", File Path: " + entry.getValue());
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/map/SourceTypeMap.java b/src/main/java/com/yjdsj/common/map/SourceTypeMap.java
new file mode 100644
index 0000000..eff1378
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/map/SourceTypeMap.java
@@ -0,0 +1,27 @@
+package com.yjdsj.common.map;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @date 2024/8/20 11:04
+ */
+public class SourceTypeMap {
+ /**
+ * 文件类型映射
+ */
+ private static Map SourceType = new HashMap<>();
+
+ /**
+ * 初始化
+ */
+ static {
+ SourceType.put("clt", "tileset");
+ }
+
+ // 获取资源对应的类型
+ public static String getSourceType(String fileExtension) {
+ return SourceType.get(fileExtension);
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/utils/DecompressionUtil.java b/src/main/java/com/yjdsj/common/utils/DecompressionUtil.java
new file mode 100644
index 0000000..444ece4
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/utils/DecompressionUtil.java
@@ -0,0 +1,23 @@
+package com.yjdsj.common.utils;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class DecompressionUtil {
+
+ /**
+ * 将压缩数据解压并转换为字符串
+ *
+ * @param compressedData
+ * @return
+ */
+ public static String decompressAndConvertToString(byte[] compressedData) {
+ try {
+ byte[] decompressedData = GzipDecompressorUtil.decompress(compressedData);
+ return new String(decompressedData, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "Failed to decompress data: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/utils/GzipDecompressorUtil.java b/src/main/java/com/yjdsj/common/utils/GzipDecompressorUtil.java
new file mode 100644
index 0000000..73dca6d
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/utils/GzipDecompressorUtil.java
@@ -0,0 +1,30 @@
+package com.yjdsj.common.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+
+public class GzipDecompressorUtil {
+
+ /**
+ * 解压数据
+ *
+ * @param compressedData
+ * @return
+ * @throws IOException
+ */
+ public static byte[] decompress(byte[] compressedData) throws IOException {
+ try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedData);
+ GZIPInputStream gzipInputStream = new GZIPInputStream(byteArrayInputStream);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
+
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = gzipInputStream.read(buffer)) != -1) {
+ byteArrayOutputStream.write(buffer, 0, len);
+ }
+ return byteArrayOutputStream.toByteArray();
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/utils/JacksonUtil.java b/src/main/java/com/yjdsj/common/utils/JacksonUtil.java
new file mode 100644
index 0000000..0881174
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/utils/JacksonUtil.java
@@ -0,0 +1,7 @@
+package com.yjdsj.common.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JacksonUtil {
+ public static final ObjectMapper mapper = new ObjectMapper();
+}
diff --git a/src/main/java/com/yjdsj/common/utils/MD5HashUtil.java b/src/main/java/com/yjdsj/common/utils/MD5HashUtil.java
new file mode 100644
index 0000000..b0689b7
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/utils/MD5HashUtil.java
@@ -0,0 +1,36 @@
+package com.yjdsj.common.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5HashUtil {
+
+ /**
+ * 将输入字符串转换为MD5哈希值
+ *
+ * @param input 要转换的字符串
+ * @return 转换后的MD5哈希值
+ */
+ public static String toMD5(String input) {
+ try {
+ // 获取MD5算法实例
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ // 将输入字符串转换为字节数组
+ byte[] messageDigest = md.digest(input.getBytes());
+
+ // 将字节数组转换为十六进制字符串
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : messageDigest) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) hexString.append('0');
+ hexString.append(hex);
+ }
+
+ // 返回MD5哈希值
+ return hexString.toString();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("MD5算法不存在", e);
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/utils/PakTableName.java b/src/main/java/com/yjdsj/common/utils/PakTableName.java
new file mode 100644
index 0000000..12bc7cc
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/utils/PakTableName.java
@@ -0,0 +1,21 @@
+package com.yjdsj.common.utils;
+
+public class PakTableName {
+ /**
+ * 根据坐标动态计算获取表名
+ *
+ * @param z
+ * @param x
+ * @param y
+ * @return
+ */
+ public static String getTableName(int z, int x, int y) {
+ if (z < 10) {
+ return "blocks";
+ } else {
+ double tx = Math.floorDiv(x, 512);
+ double ty = Math.floorDiv(y, 512);
+ return "blocks_" + Integer.toString(z) + "_" + Integer.toString((int) tx) + "_" + Integer.toString((int) ty);
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/common/utils/PositionUtil.java b/src/main/java/com/yjdsj/common/utils/PositionUtil.java
new file mode 100644
index 0000000..81af563
--- /dev/null
+++ b/src/main/java/com/yjdsj/common/utils/PositionUtil.java
@@ -0,0 +1,51 @@
+package com.yjdsj.common.utils;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+public class PositionUtil {
+ // 定义所需的常量,这些值需要根据实际应用场景设置
+ private static final double a = 6378137.0; // 椭球长半轴
+ private static final double e = 0.0818191908426; // 第一偏心率
+ private static final double epsilon = 1e-8; // 迭代精度
+ private static final double r2d = 180.0 / Math.PI; // 弧度转角度
+
+ /**
+ * 存储 BLH 转换结果的内部类
+ */
+ @Data
+ @AllArgsConstructor
+ public static class BlhResult {
+ public double lon; // 经度
+ public double lat; // 纬度
+ public double alt; // 高度
+ }
+
+ /**
+ * 将XYZ坐标转换为BLH坐标
+ */
+ public static BlhResult xyz2Blh(double x, double y, double z) {
+ double tmpX = x;
+ double tmpY = y;
+ double tmpZ = z;
+
+ double curB = 0.0;
+ double N = 0.0;
+ double calB = Math.atan2(tmpZ, Math.sqrt(tmpX * tmpX + tmpY * tmpY));
+
+ int counter = 0;
+ while (Math.abs(curB - calB) * r2d > epsilon && counter < 25) {
+ curB = calB;
+ N = a / Math.sqrt(1 - e * e * Math.sin(curB) * Math.sin(curB));
+ calB = Math.atan2(tmpZ + N * e * e * Math.sin(curB),
+ Math.sqrt(tmpX * tmpX + tmpY * tmpY));
+ counter++;
+ }
+
+ double longitude = Math.atan2(tmpY, tmpX) * r2d;
+ double latitude = curB * r2d;
+ double height = tmpZ / Math.sin(curB) - N * (1 - e * e);
+
+ return new BlhResult(longitude, latitude, height);
+ }
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/controller/MbtilesController.java b/src/main/java/com/yjdsj/mbtiles/controller/MbtilesController.java
new file mode 100644
index 0000000..d7306d6
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/controller/MbtilesController.java
@@ -0,0 +1,59 @@
+package com.yjdsj.mbtiles.controller;
+
+import com.yjdsj.mbtiles.domain.MbtilesTiles;
+import com.yjdsj.mbtiles.service.MbtilesService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+
+@Slf4j
+@RestController
+@Api(tags = "图层文件相关")
+@RequestMapping("/data/mbtiles")
+public class MbtilesController {
+
+ @Resource
+ private MbtilesService mbtilesService;
+
+ @ApiOperation(value = "获取图层资源列表")
+ @GetMapping("/list")
+ public Object list() {
+ return mbtilesService.getMbtilesSourceList();
+ }
+
+ @ApiOperation(value = "获取图层资源的详情")
+ @GetMapping("/detail/{mark}")
+ public Object detail(@PathVariable @ApiParam(value = "资源标识") String mark) {
+ return mbtilesService.detail(mark);
+ }
+
+ @GetMapping("/{mark}/{z}/{x}/{y}.{format}")
+ public ResponseEntity getData(
+ @PathVariable @ApiParam(value = "资源标识") String mark,
+ @PathVariable("z") int z,
+ @PathVariable("x") int x,
+ @PathVariable("y") int y,
+ @PathVariable("format") String format) {
+
+ MbtilesTiles mbtilesTiles = mbtilesService.getTileData(mark, z, x, y);
+ if (mbtilesTiles == null) {
+ return ResponseEntity.notFound().build();
+ }
+
+ // 获取响应媒体类型
+ String contentType = mbtilesService.getContentTypeByFormat(format);
+ return ResponseEntity.ok()
+ .contentType(MediaType.parseMediaType(contentType))
+ .body(mbtilesTiles.getTileData());
+ }
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/domain/MbtilesMetaData.java b/src/main/java/com/yjdsj/mbtiles/domain/MbtilesMetaData.java
new file mode 100644
index 0000000..0667c74
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/domain/MbtilesMetaData.java
@@ -0,0 +1,9 @@
+package com.yjdsj.mbtiles.domain;
+
+import lombok.Data;
+
+@Data
+public class MbtilesMetaData {
+ private String name;
+ private String value;
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/domain/MbtilesTiles.java b/src/main/java/com/yjdsj/mbtiles/domain/MbtilesTiles.java
new file mode 100644
index 0000000..131e6f9
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/domain/MbtilesTiles.java
@@ -0,0 +1,12 @@
+package com.yjdsj.mbtiles.domain;
+
+
+import lombok.Data;
+
+@Data
+public class MbtilesTiles {
+ private String zoomLevel;
+ private String tileColumn;
+ private String tileRow;
+ private byte[] tileData;
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/mapper/MbtilesMetaDataMapper.java b/src/main/java/com/yjdsj/mbtiles/mapper/MbtilesMetaDataMapper.java
new file mode 100644
index 0000000..bcc0b35
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/mapper/MbtilesMetaDataMapper.java
@@ -0,0 +1,11 @@
+package com.yjdsj.mbtiles.mapper;
+
+import com.yjdsj.mbtiles.domain.MbtilesMetaData;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MbtilesMetaDataMapper {
+ List selectList();
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/mapper/MbtilesTilesMapper.java b/src/main/java/com/yjdsj/mbtiles/mapper/MbtilesTilesMapper.java
new file mode 100644
index 0000000..08fdbcf
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/mapper/MbtilesTilesMapper.java
@@ -0,0 +1,11 @@
+package com.yjdsj.mbtiles.mapper;
+
+import com.yjdsj.mbtiles.domain.MbtilesTiles;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+@Mapper
+public interface MbtilesTilesMapper {
+ MbtilesTiles selectByCondition(@Param("z") int z, @Param("x") int x, @Param("y") int y);
+
+ int selectByZoomLevel(@Param("zoomLevel") int zoomLevel);
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/service/MbtilesService.java b/src/main/java/com/yjdsj/mbtiles/service/MbtilesService.java
new file mode 100644
index 0000000..9883785
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/service/MbtilesService.java
@@ -0,0 +1,132 @@
+package com.yjdsj.mbtiles.service;
+
+import com.yjdsj.common.ds.DynamicDataSourceContextHolder;
+import com.yjdsj.common.map.SourceMap;
+import com.yjdsj.mbtiles.domain.MbtilesMetaData;
+import com.yjdsj.mbtiles.domain.MbtilesTiles;
+import com.yjdsj.mbtiles.mapper.MbtilesMetaDataMapper;
+import com.yjdsj.mbtiles.mapper.MbtilesTilesMapper;
+import com.yjdsj.mbtiles.vo.MbtilesIndexVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class MbtilesService {
+
+ @Resource
+ private MbtilesMetaDataMapper mbtilesMetaDataMapper;
+ @Resource
+ private MbtilesTilesMapper mbtilesTilesMapper;
+ @Resource
+ private SourceMap sourceMap;
+
+
+ /**
+ * 获取图层资源列表
+ */
+ public Map getMbtilesSourceList() {
+ return sourceMap.getMBTilesSourceList();
+ }
+
+ /**
+ * 构建图层资源索引信息
+ */
+ public MbtilesIndexVo detail(String mark) {
+ String XYZ = "xyz";
+ String WEBMERCATORTILINGSCHEME = "WebMercatorTilingScheme";
+ String GEOGRAPHICTILINGSCHEME = "GeographicTilingScheme";
+
+ MbtilesIndexVo mbtilesIndexVo = new MbtilesIndexVo();
+ DynamicDataSourceContextHolder.setDataSourceKey(mark);
+ try {
+ // 获取元数据并转换为Map
+ List mbtilesMetaDataList = mbtilesMetaDataMapper.selectList();
+ Map metaDataMap = new HashMap<>(mbtilesMetaDataList.size());
+ for (MbtilesMetaData meta : mbtilesMetaDataList) {
+ metaDataMap.put(meta.getName(), meta.getValue());
+ }
+
+ // 处理边界信息
+ String[] boundsArray = metaDataMap.get("bounds").split(",");
+
+ // 处理缩放级别
+ Map zoomLevelCounts = new HashMap<>(31);
+ for (int i = 0; i <= 30; i++) {
+ zoomLevelCounts.put(i, mbtilesTilesMapper.selectByZoomLevel(i));
+ }
+
+ // 计算最大最小 zoomLevel
+ int minZoomLevel = zoomLevelCounts.entrySet().stream()
+ .filter(entry -> entry.getValue() > 0)
+ .map(Map.Entry::getKey)
+ .min(Integer::compare)
+ .orElse(Integer.MAX_VALUE);
+ int maxZoomLevel = zoomLevelCounts.entrySet().stream()
+ .filter(entry -> entry.getValue() > 0)
+ .map(Map.Entry::getKey)
+ .max(Integer::compare)
+ .orElse(Integer.MIN_VALUE);
+
+ // 构建URL
+ String format = metaDataMap.getOrDefault("format", "png");
+ String url = String.format("/data/mbtiles/%s/{z}/{x}/{y}.%s", mark, format);
+
+ // 设置返回对象属性
+ mbtilesIndexVo.setUrl(url);
+ mbtilesIndexVo.setMinZoom(minZoomLevel);
+ mbtilesIndexVo.setMaxZoom(maxZoomLevel);
+ mbtilesIndexVo.setWest(boundsArray[0]);
+ mbtilesIndexVo.setSouth(boundsArray[1]);
+ mbtilesIndexVo.setEast(boundsArray[2]);
+ mbtilesIndexVo.setNorth(boundsArray[3]);
+
+ // 处理加载方式
+ mbtilesIndexVo.setLoadMethod(metaDataMap.getOrDefault("load_method", XYZ));
+ // 处理投影名称(优先级: profile 包含 longlat > schema_name > 默认)
+ String schemaName = metaDataMap.getOrDefault("schema_name", WEBMERCATORTILINGSCHEME);
+ if (metaDataMap.containsKey("profile") && metaDataMap.get("profile").contains("+proj=longlat")) {
+ schemaName = GEOGRAPHICTILINGSCHEME;
+ }
+ mbtilesIndexVo.setSchemaName(schemaName);
+ mbtilesIndexVo.setFileType("layer");
+ return mbtilesIndexVo;
+ } finally {
+ DynamicDataSourceContextHolder.clearDataSourceKey();
+ }
+ }
+
+
+ /**
+ * 获取瓦片数据
+ */
+ public MbtilesTiles getTileData(String mark, int z, int x, int y) {
+ // 坐标翻转
+ int flippedY = (int) (Math.pow(2, z) - 1 - y);
+ // 获取数据源
+ DynamicDataSourceContextHolder.setDataSourceKey(mark);
+ try {
+ return mbtilesTilesMapper.selectByCondition(z, x, flippedY);
+ } finally {
+ DynamicDataSourceContextHolder.clearDataSourceKey();
+ }
+ }
+
+ /**
+ * 根据格式获取对应的媒体类型
+ */
+ public String getContentTypeByFormat(String format) {
+ if ("png".equals(format)) {
+ return "image/png";
+ } else if ("jpg".equals(format) || "jpeg".equals(format)) {
+ return "image/jpeg";
+ } else {
+ return "application/octet-stream";
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/mbtiles/vo/MbtilesIndexVo.java b/src/main/java/com/yjdsj/mbtiles/vo/MbtilesIndexVo.java
new file mode 100644
index 0000000..9b8797f
--- /dev/null
+++ b/src/main/java/com/yjdsj/mbtiles/vo/MbtilesIndexVo.java
@@ -0,0 +1,19 @@
+package com.yjdsj.mbtiles.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class MbtilesIndexVo {
+ private String url;
+ private Integer minZoom;
+ private Integer maxZoom;
+ private String west;
+ private String south;
+ private String east;
+ private String north;
+ private String loadMethod;
+ private String schemaName;
+ private String fileType;
+}
diff --git a/src/main/java/com/yjdsj/pak/controller/PakController.java b/src/main/java/com/yjdsj/pak/controller/PakController.java
new file mode 100644
index 0000000..07ea27b
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/controller/PakController.java
@@ -0,0 +1,73 @@
+package com.yjdsj.pak.controller;
+
+
+import com.yjdsj.pak.service.PakService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+
+@Slf4j
+@RestController
+@Api(tags = "地形资源相关")
+@RequestMapping("/data/pak")
+public class PakController {
+ @Resource
+ private PakService pakService;
+
+ @ApiOperation(value = "获取地形资源列表")
+ @GetMapping("/list")
+ public Object list() {
+ return pakService.getPakSourceList();
+ }
+
+ @ApiOperation(value = "获取地形资源的详情")
+ @GetMapping("/detail/{mark}")
+ public Object cltIndex(@PathVariable @ApiParam(value = "资源标识") String mark) {
+ return pakService.buildPakIndex(mark);
+ }
+
+ @ApiOperation(value = "获取JSON数据")
+ @GetMapping("/{mark}/layer.json")
+ public ResponseEntity jsonBySourceIdAndParam(@PathVariable("mark") @ApiParam(value = "资源标识") String mark) {
+ String layerJson = pakService.getCachedLayerJson(mark);
+ return layerJson != null ? ResponseEntity.ok(layerJson) : ResponseEntity.notFound().build();
+ }
+
+ @ApiOperation(value = "获取地形瓦片数据")
+ @GetMapping("/{mark}/{z}/{x}/{y}.{suffix}")
+ public ResponseEntity> data(
+ @PathVariable("mark") @ApiParam(value = "资源标识") String mark,
+ @PathVariable("z") @ApiParam(value = "缩放级别") int z,
+ @PathVariable("x") @ApiParam(value = "X轴坐标") int x,
+ @PathVariable("y") @ApiParam(value = "Y轴坐标") int y,
+ @PathVariable("suffix") @ApiParam(value = "文件后缀") String suffix) {
+
+ try {
+ // 调用 Service 获取处理后的瓦片数据
+ byte[] tileData = pakService.getTileData(mark, z, x, y);
+ if (tileData == null) {
+ return ResponseEntity.notFound().build();
+ }
+
+ // 根据文件后缀设置正确的Content-Type
+ MediaType mediaType = "png".equalsIgnoreCase(suffix) ?
+ MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG;
+
+ return ResponseEntity.ok()
+ .contentType(mediaType)
+ .body(tileData);
+ } catch (IOException e) {
+ return ResponseEntity.status(500).body("瓦片数据处理失败: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/pak/domain/PakBlocks.java b/src/main/java/com/yjdsj/pak/domain/PakBlocks.java
new file mode 100644
index 0000000..ff9e938
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/domain/PakBlocks.java
@@ -0,0 +1,11 @@
+package com.yjdsj.pak.domain;
+
+import lombok.Data;
+
+@Data
+public class PakBlocks {
+ private String z;
+ private String x;
+ private String y;
+ private byte[] tile;
+}
diff --git a/src/main/java/com/yjdsj/pak/domain/PakInfos.java b/src/main/java/com/yjdsj/pak/domain/PakInfos.java
new file mode 100644
index 0000000..1542ef6
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/domain/PakInfos.java
@@ -0,0 +1,22 @@
+package com.yjdsj.pak.domain;
+
+import lombok.Data;
+@Data
+public class PakInfos {
+ private String minX;
+ private String minY;
+ private String maxX;
+ private String maxY;
+ private String minLevel;
+ private String maxLevel;
+ private String source;
+ private String type;
+ private String tiletrans;
+ private String zip;
+ private String curLevel;
+ private String curX;
+ private String curY;
+ private byte[] layerJson;
+ private String contentType;
+ private Integer proj;
+}
diff --git a/src/main/java/com/yjdsj/pak/mapper/PakBlocksMapper.java b/src/main/java/com/yjdsj/pak/mapper/PakBlocksMapper.java
new file mode 100644
index 0000000..25a0e90
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/mapper/PakBlocksMapper.java
@@ -0,0 +1,9 @@
+package com.yjdsj.pak.mapper;
+
+import com.yjdsj.pak.domain.PakBlocks;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PakBlocksMapper {
+ PakBlocks selectByCondition(String tableName, int z, int x, int y);
+}
diff --git a/src/main/java/com/yjdsj/pak/mapper/PakInfosMapper.java b/src/main/java/com/yjdsj/pak/mapper/PakInfosMapper.java
new file mode 100644
index 0000000..764f7ef
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/mapper/PakInfosMapper.java
@@ -0,0 +1,9 @@
+package com.yjdsj.pak.mapper;
+
+import com.yjdsj.pak.domain.PakInfos;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PakInfosMapper {
+ PakInfos selectInfo();
+}
diff --git a/src/main/java/com/yjdsj/pak/service/PakService.java b/src/main/java/com/yjdsj/pak/service/PakService.java
new file mode 100644
index 0000000..eab0111
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/service/PakService.java
@@ -0,0 +1,134 @@
+package com.yjdsj.pak.service;
+
+import com.yjdsj.common.ds.DynamicDataSourceContextHolder;
+import com.yjdsj.common.map.SourceMap;
+import com.yjdsj.common.utils.GzipDecompressorUtil;
+import com.yjdsj.common.utils.PakTableName;
+import com.yjdsj.pak.domain.PakBlocks;
+import com.yjdsj.pak.domain.PakInfos;
+import com.yjdsj.pak.mapper.PakBlocksMapper;
+import com.yjdsj.pak.mapper.PakInfosMapper;
+import com.yjdsj.pak.vo.PakIndexVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class PakService {
+ @Resource
+ private SourceMap sourceMap;
+ @Resource
+ private PakInfosMapper pakInfosMapper;
+ @Resource
+ private PakBlocksMapper pakBlocksMapper;
+
+ // 资源压缩状态缓存
+ private final Map isZipCache = new HashMap<>();
+ // JSON数据缓存
+ private final Map jsonCache = new HashMap<>();
+
+ /**
+ * 获取地形资源列表
+ */
+ public Map getPakSourceList() {
+ return sourceMap.getPakSourceList();
+ }
+
+ /**
+ * 构建地形资源索引信息(含URL、元数据)并更新缓存
+ */
+ public PakIndexVo buildPakIndex(String mark) {
+ final String XYZ = "xyz";
+ final String TMS = "tms";
+ final String TERRAIN_TYPE = "terrain";
+ final String WEBMERCATORTILINGSCHEME = "WebMercatorTilingScheme";
+ final String GEOGRAPHICTILINGSCHEME = "GeographicTilingScheme";
+ final int PROJ_4326 = 4326;
+
+ DynamicDataSourceContextHolder.setDataSourceKey(mark);
+ try {
+ // 查询资源元数据并校验
+ PakInfos pakInfos = pakInfosMapper.selectInfo();
+ if (pakInfos == null) {
+ return null;
+ }
+ // 复制属性并初始化VO
+ PakIndexVo pakIndexVo = new PakIndexVo();
+ BeanUtils.copyProperties(pakInfos, pakIndexVo);
+ // 设置地理坐标范围
+ pakIndexVo.setWest(pakInfos.getMinX());
+ pakIndexVo.setSouth(pakInfos.getMinY());
+ pakIndexVo.setEast(pakInfos.getMaxX());
+ pakIndexVo.setNorth(pakInfos.getMaxY());
+ // 设置加载方式
+ String tiletrans = pakInfos.getTiletrans();
+ pakIndexVo.setLoadMethod(TMS.equals(tiletrans) ? TMS : XYZ);
+ // 优化投影名称设置
+ pakIndexVo.setSchemaName(WEBMERCATORTILINGSCHEME);
+ Integer proj = pakInfos.getProj();
+ if (proj != null && proj == PROJ_4326) {
+ pakIndexVo.setSchemaName(GEOGRAPHICTILINGSCHEME);
+ }
+ // 构建资源访问URL
+ String baseUrl = String.format("/data/pak/%s", mark);
+ if (TERRAIN_TYPE.equals(pakInfos.getType())) {
+ pakIndexVo.setUrl(baseUrl);
+ pakIndexVo.setFileType("terrain");
+ } else {
+ // 安全处理 contentType 解析
+ String contentType = pakInfos.getContentType();
+ String suffix = "unknown";
+ if (contentType != null && contentType.contains("/")) {
+ String[] typeParts = contentType.split("/");
+ if (typeParts.length > 1) {
+ suffix = typeParts[1];
+ }
+ }
+ pakIndexVo.setUrl(baseUrl + "/{z}/{x}/{y}." + suffix);
+ pakIndexVo.setFileType("layer");
+ }
+ // 缓存更新
+ String zipFlag = pakInfos.getZip();
+ isZipCache.put(mark, zipFlag != null && Integer.parseInt(zipFlag) > 0);
+ jsonCache.put(mark, new String(pakInfos.getLayerJson() != null ? pakInfos.getLayerJson() : new byte[0]));
+ return pakIndexVo;
+ } finally {
+ // 确保数据源清理
+ DynamicDataSourceContextHolder.clearDataSourceKey();
+ }
+ }
+
+ /**
+ * 获取缓存的 layer.json 数据
+ */
+ public String getCachedLayerJson(String mark) {
+ return jsonCache.get(mark);
+ }
+
+ /**
+ * 根据坐标获取瓦片数据(含压缩处理)
+ */
+ public byte[] getTileData(String mark, int z, int x, int y) throws IOException {
+ DynamicDataSourceContextHolder.setDataSourceKey(mark);
+ try {
+ // 确定目标表名(通过工具类计算)
+ String tableName = PakTableName.getTableName(z, x, y);
+ // 查询瓦片数据
+ PakBlocks pakBlocks = pakBlocksMapper.selectByCondition(tableName, z, x, y);
+ if (pakBlocks == null) {
+ return null;
+ }
+ // 处理压缩数据(根据缓存的压缩状态)
+ Boolean isZip = isZipCache.getOrDefault(mark, false);
+ return isZip ? GzipDecompressorUtil.decompress(pakBlocks.getTile()) : pakBlocks.getTile();
+ } finally {
+ DynamicDataSourceContextHolder.clearDataSourceKey();
+ }
+ }
+}
diff --git a/src/main/java/com/yjdsj/pak/vo/PakIndexVo.java b/src/main/java/com/yjdsj/pak/vo/PakIndexVo.java
new file mode 100644
index 0000000..9ae6a8d
--- /dev/null
+++ b/src/main/java/com/yjdsj/pak/vo/PakIndexVo.java
@@ -0,0 +1,19 @@
+package com.yjdsj.pak.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class PakIndexVo {
+ private String west;
+ private String south;
+ private String east;
+ private String north;
+ private String minLevel;
+ private String maxLevel;
+ private String url;
+ private String type;
+ private String loadMethod;
+ private String schemaName;
+ private String fileType;
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..9cbecf9
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,3 @@
+server:
+ host: 127.0.0.1
+ port: 8080
diff --git a/src/main/resources/mapper/CltInfosMapper.xml b/src/main/resources/mapper/CltInfosMapper.xml
new file mode 100644
index 0000000..7926be3
--- /dev/null
+++ b/src/main/resources/mapper/CltInfosMapper.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/CltTilesMapper.xml b/src/main/resources/mapper/CltTilesMapper.xml
new file mode 100644
index 0000000..b61a12e
--- /dev/null
+++ b/src/main/resources/mapper/CltTilesMapper.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/MbtilesMetaDataMapper.xml b/src/main/resources/mapper/MbtilesMetaDataMapper.xml
new file mode 100644
index 0000000..e12449d
--- /dev/null
+++ b/src/main/resources/mapper/MbtilesMetaDataMapper.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/MbtilesTilesMapper.xml b/src/main/resources/mapper/MbtilesTilesMapper.xml
new file mode 100644
index 0000000..36a05ef
--- /dev/null
+++ b/src/main/resources/mapper/MbtilesTilesMapper.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/PakBlocksMapper.xml b/src/main/resources/mapper/PakBlocksMapper.xml
new file mode 100644
index 0000000..c339442
--- /dev/null
+++ b/src/main/resources/mapper/PakBlocksMapper.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/mapper/PakInfosMapper.xml b/src/main/resources/mapper/PakInfosMapper.xml
new file mode 100644
index 0000000..ef73291
--- /dev/null
+++ b/src/main/resources/mapper/PakInfosMapper.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+