需求背景:
接到一个需求,需要把卫星影像tif图层切片,生成类似于金字塔的文件目录,然后供APP端下载,离线加载。地图切片其实也有其他的方案,但是由于该项目的其他地图也是发布在geoserver上的,且geoserver默认集成了GeoWebCache,遂直接使用GeoWebCache来执行切片操作。
题外话:GeoWebCache分为2个版本,一个是独立程序,一个是集成在Geoserver中。
首先,版本如下:
那么在Geoserver中,让GeoWebCache执行切片也有2种方式:
一、直接在Geoserver的管理页面操作
第一步:点击“Tile Layers”菜单,会展示当前可以切片的所有图层。如下图
第二步:找到需要切片的图层,点击“Seed/Truncate”按钮,会进入到如下页面:
上面的这个页面就是真正执行切片任务的页面了,下面的参数填报很关键,记住以下几点:
- 理论上,“Number of tasks to use” 用默认的1个或者多个都行,选多个,同一个切片任务可以并行执行,会更快;
- “Type of operation”有三种操作:seed表示执行切片,reseed表示再次执行切片,truncate表示删除切片的成果文件。
- “Grid Set”一定要选择你要切的图层对应的坐标系。默认内置了2种坐标系,如果坐标系不符合你的图层,要么先利用QGIS等将图层坐标系转成EPSG:4326,要么在Geoserver首页的“Caching Defaults”页面中添加切片的坐标系。
- “Format”:切片的成果格式,png和jpg两种,如果有其他的格式需要,也可以在“Caching Defaults”中配置。
- Zoom start和Zoom stop分别表示切片的最小和最大层级,根据图层情况自行设置,拿捏不准的,可以先在Geoserver首页的“Layer Preview”中预览要切片的图层看看,缩放哪些层级合适;
- “STYLES”:图层样式;
- “Bounding box”:可以不填,空着的话就是取坐标系的边界;
点击“submit”按钮,提交切片任务,页面会刷新,展示任务状态,如下图:
那么问题来了,切好的图在电脑或服务器的哪个位置呢?可以通过访问:http://IP地址:端口/geoserver/gwc
进行查看,在页面底部可以看到,如下图:
打开成果目录,如下:
可以看到文件夹是以切片的图层名称命名的,目录下面就是根据zoom缩放层级分类的文件夹了:
二、利用RESTFUL API接口进行操作
如果想让切片更加自动化,刚好GeoWebCache提供了REST API操作。文档地址:Seeding and Truncating — GeoServer 2.24.x User Manual
吐槽下文档写得很有问题,我按照官方提供的示例尝试过很多次提交执行切片任务都无法成功。最后发现既然在Geoserver管理页面提交执行切片任务可以成功,索性F12查看到底执行的请求是啥。然后整理成了Spring后台请求代码。经过验证成功。
java代码:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import java.io.IOException;
import java.util.*;
import static org.toilelibre.libe.curl.Curl.curl;
/**
* @author mason
* @version 1.0
* @description: TODO
* @date 2023/6/14 15:49
*/
@Component
public class GeoWebCache {
private static String url;
private static String geoUsername;
private static String geoPassword;
//工作区间
public static String workspace;
@Value("${geoserver.url}")
public void setUrl(String url) {
GeoWebCache.url = url;
}
@Value("${geoserver.username}")
public void setGeoUsername(String geoUsername) {
GeoWebCache.geoUsername = geoUsername;
}
@Value("${geoserver.password}")
public void setGeoPassword(String geoPassword) {
GeoWebCache.geoPassword = geoPassword;
}
@Value("${geoserver.workspace}")
public void setWorkspace(String workspace) {
GeoWebCache.workspace = workspace;
}
/**
* 获取geoWebCache中的图层
*
* @return List
*/
public static List<String> getLayers() {
Map<String, List<String>> map = new HashMap();
String cmd = "curl -u " + geoUsername + ":" + geoPassword + " \"" + url + "/gwc/rest/layers\"";
HttpResponse curl = curl(cmd);
HttpEntity entity = curl.getEntity();
List<String> list = new ArrayList<>();
if (entity != null) {
try {
String result = EntityUtils.toString(entity, "UTF-8");
JSONArray jsonArray = JSONArray.parseArray(result);
String jsonString = JSONArray.toJSONString(jsonArray);
list = JSONArray.parseArray(jsonString, String.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
/**
* 指定图层进行切片操作
* @param param
* @return
*/
public static R<Void> slice(String layer, @Validated GeoWebCacheParam param) {
Map<String, String> requestMap = JSON.parseObject(JSON.toJSONString(param), HashMap.class);
HashMap<String, String> headerMap = new HashMap<>();
headerMap.put("Authorization", "Basic " + Base64.getUrlEncoder().encodeToString((geoUsername + ":" + geoPassword).getBytes()));
String result = null;
try {
result = HttpRequestUtil.post(requestMap, url + "/gwc/rest/seed/" + layer , headerMap, "application/x-www-form-urlencoded");
} catch (Exception e) {
return R.fail("请求切片失败:" + e);
}
return R.ok(result);
}
/**
* 获取切片的情况
*
* @param layer 指定图层
* @return Map
*/
public static R<Object> getSliceStatus(String layer) {
//返回所有图层切片情况 curl -u <user>:<password> -XGET http://localhost:8080/geoserver/gwc/rest/seed.json
//返回指定图层的切片情况
String cmd = "curl -u " + geoUsername + ":" + geoPassword + " -XGET " + url + "/gwc/rest/seed/" + layer + ".json";
HttpResponse curl = curl(cmd);
StatusLine statusLine = curl.getStatusLine();
if ("HTTP/1.1 200 OK".equals(statusLine.toString())) {
HttpEntity entity = curl.getEntity();
try {
String result = EntityUtils.toString(entity, "UTF-8");
return R.ok(result);
} catch (IOException e) {
return R.fail("解析返回结果失败:" + e);
}
}
return R.fail("查询失败");
}
}
yml中的geoserver配置:
请求工具类:
/**
* @author mason
* @version 1.0
* @description: TODO
* @date 2023/6/15 11:11
*/
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* http请求工具类
* @ClassName:HttpRequestUtil
* @Description: Http请求
*/
public class HttpRequestUtil {
private String defaultContentEncoding;
public HttpRequestUtil() {
this.defaultContentEncoding = Charset.defaultCharset().name();
}
/**
* 默认的响应字符集
*/
public String getDefaultContentEncoding() {
return this.defaultContentEncoding;
}
/**
* 设置默认的响应字符集
*/
public void setDefaultContentEncoding(String defaultContentEncoding) {
this.defaultContentEncoding = defaultContentEncoding;
}
public static String post(JSONObject json, String url) throws Exception{
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
StringEntity s = new StringEntity(json.toString(),"utf-8");
s.setContentEncoding("UTF-8");
/*发送json数据需要设置contentType*/
s.setContentType("application/json");
post.setEntity(s);
post.setHeader("Content-Type","application/json;charset=utf-8");
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
// System.out.println("返回数据="+result);
} catch (Exception e) {
//System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
if(null != br) br.close();
if(null != br) in.close();
if(null != response) response.close();
if(null != httpclient) httpclient.close();
}
return result;
}
public static String post(JSONObject json, String url, Map<String, String> headerMap) throws Exception{
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
StringEntity s = new StringEntity(json.toString(),"utf-8");
s.setContentEncoding("UTF-8");
/*发送json数据需要设置contentType*/
s.setContentType("application/json");
post.setEntity(s);
post.setHeader("Content-Type","application/json;charset=utf-8");
Set<Entry<String, String>> headerEntries = headerMap.entrySet();
for (Entry<String, String> headerEntry:headerEntries){
post.setHeader(headerEntry.getKey(), headerEntry.getValue());
}
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
//System.out.println("返回数据="+result);
} catch (Exception e) {
//System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
br.close();
in.close();
response.close();
httpclient.close();
}
return result;
}
/**
* ContentType.URLENCODED.getHeader()
* @param map
* @param url
* @param headerMap
* @param contentType
* @return
* @throws Exception
*/
public static String post(Map<String, String> map, String url, Map<String, String> headerMap, String contentType) throws Exception{
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
List<NameValuePair> nameValuePairs = getNameValuePairList(map);
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nameValuePairs, "UTF-8");
/*发送json数据需要设置contentType*/
urlEncodedFormEntity.setContentType(contentType);
post.setEntity(urlEncodedFormEntity);
post.setHeader("Content-Type", contentType);
Set<Entry<String, String>> headerEntries = headerMap.entrySet();
for (Entry<String, String> headerEntry:headerEntries){
post.setHeader(headerEntry.getKey(), headerEntry.getValue());
}
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
//System.out.println("返回数据="+result);
} catch (Exception e) {
//System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
br.close();
in.close();
response.close();
httpclient.close();
}
return result;
}
private static List<NameValuePair> getNameValuePairList(Map<String, String> map) {
List<NameValuePair> list = new ArrayList<>();
for(String key : map.keySet()) {
list.add(new BasicNameValuePair(key,map.get(key)));
}
return list;
}
public static String post(String params, String url, Map<String, String> headerMap) throws Exception{
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
StringEntity s = new StringEntity(params.toString(),"utf-8");
s.setContentEncoding("UTF-8");
/*发送json数据需要设置contentType*/
s.setContentType("application/json");
post.setEntity(s);
post.setHeader("Content-Type","application/json;charset=utf-8");
Set<Entry<String, String>> headerEntries = headerMap.entrySet();
for (Entry<String, String> headerEntry:headerEntries){
post.setHeader(headerEntry.getKey(), headerEntry.getValue());
}
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
//System.out.println("返回数据="+result);
} catch (Exception e) {
//System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
br.close();
in.close();
response.close();
httpclient.close();
}
return result;
}
public static String put(JSONObject json, String url, Map<String, String> headerMap) throws Exception {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpPut post = new HttpPut(url);
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
StringEntity s = new StringEntity(json.toString(),"utf-8");
s.setContentEncoding("UTF-8");
/*发送json数据需要设置contentType*/
s.setContentType("application/json");
post.setEntity(s);
post.setHeader("Content-Type","application/json;charset=utf-8");
Set<Entry<String, String>> headerEntries = headerMap.entrySet();
for (Entry<String, String> headerEntry:headerEntries){
post.setHeader(headerEntry.getKey(), headerEntry.getValue());
}
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
//System.out.println("返回数据="+result);
} catch (Exception e) {
//System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
br.close();
in.close();
response.close();
httpclient.close();
}
return result;
}
public static String delete(String url, Map<String, String> headerMap) throws Exception {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpDelete post = new HttpDelete(url);
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
post.setHeader("Content-Type","application/json;charset=utf-8");
Set<Entry<String, String>> headerEntries = headerMap.entrySet();
for (Entry<String, String> headerEntry:headerEntries){
post.setHeader(headerEntry.getKey(), headerEntry.getValue());
}
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
//System.out.println("返回数据="+result);
} catch (Exception e) {
//System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
br.close();
in.close();
response.close();
httpclient.close();
}
return result;
}
public static String get(JSONObject paramsObj, String url, Map<String, String> headerMap) throws Exception {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
CloseableHttpResponse response = null;
InputStream in = null;
BufferedReader br = null;
String result = "";
try {
StringBuffer param = new StringBuffer();
int i = 0;
Set<Entry<String, Object>> entries = paramsObj.entrySet();
for (Entry<String, Object> entry:entries){
if (i == 0)
param.append("?");
else
param.append("&");
param.append(entry.getKey()).append("=").append(entry.getValue());
i++;
}
url += param;
HttpGet post = new HttpGet(url);
// post.setHeader("Content-Type","application/json;charset=utf-8");
Set<Entry<String, String>> headerEntries = headerMap.entrySet();
for (Entry<String, String> headerEntry:headerEntries){
post.setHeader(headerEntry.getKey(), headerEntry.getValue());
}
response = httpclient.execute(post);
in = response.getEntity().getContent();
br = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuilder strber= new StringBuilder();
String line = null;
while((line = br.readLine())!=null){
strber.append(line+'\n');
}
result = strber.toString();
if(response.getStatusLine().getStatusCode()!=HttpStatus.SC_OK){
if(StringUtils.isBlank(result)) result = "服务器异常";
throw new Exception(result);
}
//System.out.println("返回数据="+result);
} catch (Exception e) {
// System.err.println("调用接口出错::::::::::::"+e.getMessage());
throw new Exception(e.getMessage());
} finally {
br.close();
in.close();
response.close();
httpclient.close();
}
return result;
}
}
控制层代码:
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @author mason
* @version 1.0
* @description: TODO
* @date 2023/6/14 16:11
*/
@AllArgsConstructor
@RequestMapping("/gwc")
@RestController
public class GeoWebCacheController {
/**
* 获取geoWebCache中的图层
* @return
*/
@GetMapping("/getLayers")
public List<String> getLayers() {
return GeoWebCache.getLayers();
}
/**
* 指定图层进行切片操作
* @param layer 图层名称
* @param param 请求参数
* @return
*/
@PostMapping("/slice")
public R<Void> slice(String layer, GeoWebCacheParam param) {
return GeoWebCache.slice(layer, param);
}
/**
* 获取切片的情况
*
* @param layer 指定图层
* @return Map
*/
@GetMapping("/getSliceType")
public R<Object> getSliceType(@RequestParam String layer) {
return GeoWebCache.getSliceStatus(layer);
}
}
请求参数:
import com.sw.survey.common.validate.AddGroup;
import com.sw.survey.common.validate.EditGroup;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author mason
* @version 1.0
* @description: GeoWebCache切片请求参数
* @date 2023/6/15 11:14
*/
@Data
public class GeoWebCacheParam {
/**
* 执行切片的线程数
*/
@NotBlank(message = "执行切片的线程数不能为空", groups = { AddGroup.class, EditGroup.class })
private String threadCount;
/**
* 执行任务的类型:seed:切片,reseed:重新执行切片,truncate删除切片文件
*/
private String type;
/**
* 坐标系,例如:EPSG:4491
*/
private String gridSetId;
/**
*切片的结果类型,例如:image/png
*/
private String tileFormat;
/**
* 切片文件的最小缩放层级
*/
private String zoomStart;
/**
* 切片文件的最大缩放层级
*/
private String zoomStop;
/**
* 图层样式
*/
private String parameter_STYLES;
/**
* 切片请求失败重试次数
*/
private String tileFailureRetryCount;
/**
* 切片请求失败后过多少毫秒再尝试
*/
private String tileFailureRetryWaitTime;
/**
* 中止前的总失败次数
*/
private String totalFailuresBeforeAborting;
/**
* 边界:最小X坐标
*/
private String minX;
/**
* 边界:最小Y坐标
*/
private String minY;
/**
* 边界:最大X坐标
*/
private String maxX;
/**
* 边界:最大Y坐标
*/
private String maxY;
}
模拟请求(apifox):
至此,大功告成!