Java對接geoserver實(shí)現(xiàn)圖層組與圖層發(fā)布

最近一個項(xiàng)目遇到了需要對接geoserver的需求,要定時同步固定目錄下的tif到geoserver中,并發(fā)布成圖層組給前端使用
首先需要將固定目錄mount 到服務(wù)器目錄上,這里參考了這些博客
Linux創(chuàng)建并掛載NAS:win10 https://blog.csdn.net/cyf12345678/article/details/126592249

win7 https://www.haozhuangji.com/xtjc/155916801.htmlhttps://www.xitongzhijia.net/xtjc/20111207/2349.html

https://blog.csdn.net/cyf12345678/article/details/126592249

核心的命令其實(shí)就下面這些

//liunx安裝掛載工具
yum install -y cifs-utils
//掛載共享目錄
mount -t cifs -o username=username,password=password //192.168.1.139/layer/win_share/ /data/share_data/win_share

打開文件 CIFS和SMB類似
smb://user_name:password@server_name/.... \server_name...
webdav http://ip:5005/.....
ftp://ip/....

geoserver
api文檔(英文):https://docs.geoserver.org/stable/en/user/rest/index.html#rest

docker部署:https://www.bilibili.com/read/cv17617766/

docker pull kartoza/geoserver

啟動腳本:docker run --restart=always -d -v /data/share_data:/etc/letsencrypt -p 31880:8080 --name geoserver kartoza/geoserver

注意mount之后要重新啟動一下geoserver才會出現(xiàn)新掛載的目錄:docker restart geoserver

admin的初始密碼在日志里

image.png

http://ip:31880/geoserver/web

添加用戶:admin的初始密碼在日志里,需要重新添加用戶(別忘了給新增的用戶加上角色)并設(shè)置密碼


image.png
image.png

新建工作空間

image.png

新建存儲倉庫:tif文件需要先上傳到部署geoserver的服務(wù)器的/data/share_data目錄下

image.png

點(diǎn)擊發(fā)布

圖層可以使用

以上為基礎(chǔ)設(shè)施的建設(shè),下面是代碼方面


架構(gòu).png

架構(gòu)很簡單,就是通過mount掛載電腦或者nas上的文件夾到服務(wù)器的data_share目錄下,Java程序定時掃描該目錄下的tif文件,再發(fā)布到geoserver服務(wù)上并拼裝圖層組的url給前端使用。
Maven引入geoserver-manager的sdk:

 <dependency>
            <groupId> nl.pdok</groupId>
            <artifactId>geoserver-manager</artifactId>
            <version>1.7.0-pdok2</version>
        </dependency>

geoserver配置:

import it.geosolutions.geoserver.rest.GeoServerRESTManager;
import java.net.URL;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class GeoServerConfig {
    @Resource
    private GeoServerProperties geoServerProperties;

    @Bean(name = "geoServerRESTManager")
    public GeoServerRESTManager geoServerRESTManager() {
        try {
            return new GeoServerRESTManager(new URL(geoServerProperties.getEndpoint()),
                    geoServerProperties.getUsername(), geoServerProperties.getPassword());
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.geoserver")
public class GeoServerProperties {
    private String endpoint;

    private String username;

    private String password;

    private String workSpaceName;
}

spring:
  geoserver:
    endpoint: http://${global.geoserver.ip}:${global.geoserver.port}/geoserver
    username: username
    password: password
    workSpaceName: test

具體代碼:其中需要注意的是我寫了一個規(guī)則來根據(jù)文件的目錄對應(yīng)掛載主機(jī)的ip(需要這個ip+目錄可以直接訪問到對應(yīng)主機(jī)上的文件)


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jingansi.suitation.config.GeoServerProperties;
import com.jingansi.suitation.dao.mapper.LayerGroupMapper;
import com.jingansi.suitation.dao.mapper.LayerMapper;
import com.jingansi.suitation.dao.model.Layer;
import com.jingansi.suitation.dao.model.LayerGroup;
import com.jingansi.suitation.dao.model.Point;
import com.jingansi.suitation.enums.LayerGroupAffiliation;
import com.jingansi.suitation.exception.BizException;
import com.jingansi.suitation.model.LayerGroupDTO;
import com.jingansi.suitation.model.RangeReq;
import com.jingansi.suitation.service.GeoServerService;
import com.jingansi.suitation.util.FileUtil;
import it.geosolutions.geoserver.rest.GeoServerRESTManager;
import it.geosolutions.geoserver.rest.decoder.RESTCoverageStore;
import it.geosolutions.geoserver.rest.decoder.RESTLayerGroup;
import it.geosolutions.geoserver.rest.encoder.GSLayerEncoder;
import it.geosolutions.geoserver.rest.encoder.GSLayerGroupEncoder;
import it.geosolutions.geoserver.rest.encoder.coverage.GSImageMosaicEncoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.*;

@Slf4j
@Service
public class GeoServerServiceImpl extends ServiceImpl<LayerMapper, Layer> implements GeoServerService {
    @Resource
    private GeoServerProperties geoServerProperties;
    @Resource
    private GeoServerRESTManager geoServerRESTManager;
    @Resource
    private LayerGroupMapper layerGroupMapper;
    @Resource
    private LayerMapper layerMapper;

    @Value("${mount.base.shareContainerPath:/etc/letsencrypt/}")
    private String shareContainerPath;

    @Value("${mount.base.localPathRule:win_share:192.168.1.139,win_share2:192.168.1.14,nas_share:192.168.1.19}")
    private String localPathRule;


    @Override
    public List<LayerGroupDTO> layerGroupList() {
        List<LayerGroupDTO> layerGroups = new ArrayList<>();
        LambdaQueryWrapper<LayerGroup> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(LayerGroup::getLayerGroupAffiliation, LayerGroupAffiliation.SAR);
        List<LayerGroup> sarLayerGroups = layerGroupMapper.selectList(queryWrapper);
        layerGroups.add(LayerGroupDTO.builder().layerGroupAffiliation(LayerGroupAffiliation.SAR.name()).count(sarLayerGroups.size()).data(sarLayerGroups).build());
        return layerGroups;
    }

    @Override
    public List<Layer> layerList(Long layerGroupId) {
        LambdaQueryWrapper<Layer> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Layer::getLayerGroupId, layerGroupId);
        return layerMapper.selectList(queryWrapper);
    }

/**
* 找出經(jīng)緯度在范圍里的圖層
* 需要記錄圖層的上下左右四個點(diǎn)的經(jīng)緯度
**/
    @Override
    public List<Layer> rangeList(RangeReq rangeReq) {
        List<Layer> rangeLayers = new ArrayList<>();
        LambdaQueryWrapper<Layer> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Layer::getLayerGroupId, rangeReq.getLayerGroupIds());
        //查詢圖層組的圖層
        List<Layer> groupLayers = layerMapper.selectList(queryWrapper);
        groupLayers.forEach(layer -> {
            List<Point> ps = new ArrayList<>();
            ps.add(Point.builder().longitude(layer.getTopLeftLongitude()).latitude(layer.getTopLeftLatitude()).build());
            ps.add(Point.builder().longitude(layer.getTopRightLongitude()).latitude(layer.getTopRightLatitude()).build());
            ps.add(Point.builder().longitude(layer.getDownRightLongitude()).latitude(layer.getDownRightLatitude()).build());
            ps.add(Point.builder().longitude(layer.getDownLeftLongitude()).latitude(layer.getDownLeftLatitude()).build());
            //判斷經(jīng)緯度是否在圖層內(nèi)
            if (containsLngLat(ps.toArray(new Point[4]), Point.builder().longitude(rangeReq.getLongitude()).latitude(rangeReq.getLatitude()).build())) {
                rangeLayers.add(layer);
            }
        });
        return rangeLayers;
    }


    /**
     * 判斷點(diǎn)是否在多邊形內(nèi)(該方法來自前端,不保證其正確性--部分圖像中心點(diǎn)會出現(xiàn)不在范圍內(nèi)的情況(高緯度等偏斜情形)--要順時針或者逆時針順序)
     *
     * @param {*} points [{lng,lat}]
     * @param {*} point {lng,lat}
     * @returns
     */
    public static Boolean containsLngLat(Point[] points, Point point) {
        try {
            // 數(shù)組長度
            int length = points.length;
            boolean c = false;
            for (int i = 0, j = length - 1; i < length; ) {
                if (
                        points[i].getLatitude() > point.getLatitude() != points[j].getLatitude() > point.getLatitude() &&
                                point.getLongitude() <
                                        ((points[j].getLongitude() - points[i].getLongitude()) * (point.getLatitude() - points[i].getLatitude())) /
                                                (points[j].getLatitude() - points[i].getLatitude()) +
                                                points[i].getLongitude()
                ) {
                    c = !c;
                }
                j = i;
                i += 1;
            }
            return c;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("containsLngLat point:{} points:{}", point, points);
            return false;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addLayerGroup(LayerGroup layerGroup) {
        try {
            Date now = new Date();
            List<Layer> layers = new ArrayList<>();
            //處理圖層
            ArrayList<String> layersList = handleLayer(layerGroup, layers);
            if (!CollectionUtils.isEmpty(layers)) {
                //創(chuàng)建并發(fā)布圖層組
                createLayerGroup(layerGroup.getLayerGroupName(), layersList);
                //默認(rèn)圖層組為SAR
                if (StringUtils.isBlank(layerGroup.getLayerGroupAffiliation())) {
                    layerGroup.setLayerGroupAffiliation(LayerGroupAffiliation.SAR.name());
                }
                setLayerGroupUrlAndBbox(layerGroup);
                layerGroup.setGmtCreate(now);
                layerGroup.setGmtModified(now);
                //保存圖層組
                layerGroupMapper.insert(layerGroup);
                layers.forEach(layer -> {
                    layer.setLayerGroupId(layerGroup.getId());
                    layer.setGmtCreate(now);
                    layer.setGmtModified(now);
                    layer.setGmtCreateBy(layerGroup.getGmtCreateBy());
                    layer.setGmtModifiedBy(layerGroup.getGmtModifiedBy());
                });
                //保存圖層
                saveBatch(layers);
            }
        } catch (BizException e) {
            log.error("addLayerGroup BizException error:" + JSON.toJSONString(layerGroup), e);
            throw e;
        } catch (Exception e) {
            log.error("addLayerGroup error:" + JSON.toJSONString(layerGroup), e);
            throw new BizException("新增圖層失敗");
        }
        return layerGroup.getId();
    }

    @Override
    public ArrayList<String> handleLayer(LayerGroup layerGroup, List<Layer> layers) throws Exception {
        //fileDirectory 處理
        String fileDirectory = shareContainerPath + layerGroup.getLayerGroupDirectory();
        //圖層組名稱如果為空則設(shè)置為入?yún)⑻幚砗蟮哪夸浀腗D5值---前端必填字段,走不到這里
        if (StringUtils.isBlank(layerGroup.getLayerGroupName())) {
            layerGroup.setLayerGroupName(DigestUtils.md5Hex(fileDirectory));
        }
        File layerDirectory = FileUtil.getFile(fileDirectory);
        String[] layerPaths = layerDirectory.list();
        ArrayList<String> layersList = handleLayer(layers, fileDirectory, layerPaths);
        if (CollectionUtils.isEmpty(layersList)) {
            throw new BizException("新增圖層失?。簣D層文件夾下未讀取到tif圖層");
        }
        return layersList;
    }

    @Override
    public ArrayList<String> handleLayer(List<Layer> layers, String fileDirectory, String[] layerPaths) {
        ArrayList<String> layersList = new ArrayList<>();
        Map<String, String> pathIpMap = getPathIpMap();
        if (layerPaths != null) {
            for (String layerPath : layerPaths) {
                if (StringUtils.isBlank(layerPath)) {
                    continue;
                }
                try {
                    if (layerPath.endsWith(".tif")) {
                        String layerName = layerPath.split(".tif")[0];
                        Layer layer = new Layer();
                        //解析配置文件中的目錄與ip關(guān)系
                        String ip = "";
                        for (Map.Entry<String, String> entry : pathIpMap.entrySet()) {
                            if (fileDirectory.contains(entry.getKey())) {
                                ip = entry.getValue();
                            }
                        }
                        layer.setLayerTifUrl(ip + fileDirectory.replace(shareContainerPath, "") + layerPath);
                        layer.setLayerName(layerName);
                        layersList.add(geoServerProperties.getWorkSpaceName() + ":" + layerName);
                        //推送圖層數(shù)據(jù)
                        publishTiffData(layerName, fileDirectory + "/" + layerPath);
                        layers.add(layer);
                    }
                } catch (Exception e) {
                    log.error("圖層解析失敗", e);
                }
            }
        }
        return layersList;
    }

    /**
     * 獲取ip路徑映射Map
     *
     * @return {@link Map}<{@link String}, {@link String}>
     */
    @Override
    public Map<String, String> getPathIpMap() {
        String[] rules = localPathRule.split(";");
        Map<String, String> pathIpMap = new HashMap<>(16);
        for (String rule : rules) {
            String[] pathIp = rule.split(",");
            pathIpMap.put(pathIp[0], pathIp[1]);
        }
        return pathIpMap;
    }


    private void setLayerGroupUrlAndBbox(LayerGroup layerGroup) {
        String layerGroupName = layerGroup.getLayerGroupName();
        RESTLayerGroup restLayerGroup = geoServerRESTManager.getReader().getLayerGroup(geoServerProperties.getWorkSpaceName(), layerGroupName);
        String nativeCrs = restLayerGroup.getCRS();
        layerGroup.setLayer(geoServerProperties.getWorkSpaceName() + ":" + layerGroupName);
        layerGroup.setBboxMinX(restLayerGroup.getMinX());
        layerGroup.setBboxMinY(restLayerGroup.getMinY());
        layerGroup.setBboxMaxX(restLayerGroup.getMaxX());
        layerGroup.setBboxMaxY(restLayerGroup.getMaxY());
//返回圖層預(yù)覽wms地址
        /*return geoServerProperties.getEndpoint() + "/" + geoServerProperties.getWorkSpaceName() + "/wms?service=WMS&version=1.1.0&request=GetMap&layers=" + geoServerProperties.getWorkSpaceName() + ":" + layerGroupName + "&bbox="
                + nativeMinX + "," + nativeMinY + "," + nativeMaxX + "," + nativeMaxY +
                //todo 寬高寫死了
                "&width=768&height=330&srs=" + nativeCrs + "&styles=&format=application/openlayers";*/
        //返回圖層預(yù)覽緩存gwc地址-wmts
        layerGroup.setLayerGroupUrl(geoServerProperties.getEndpoint() + "/gwc/demo/" + geoServerProperties.getWorkSpaceName() + ":" + layerGroupName + "?gridSet="
                + nativeCrs + "&format=image/png");
    }


    /**
     * 創(chuàng)建圖層組
     * @param layerGroupName 圖層組名稱
     * @param layersList     圖層名稱隊列:格式為WorkSpace:圖層名稱 eg: ja-test:tiff-test
     * @return 是否成功
     */
    private boolean createLayerGroup(String layerGroupName, ArrayList<String> layersList) {
        GSLayerGroupEncoder gsLayerGroupEncoder = new GSLayerGroupEncoder();
        gsLayerGroupEncoder.setWorkspace(geoServerProperties.getWorkSpaceName());
        gsLayerGroupEncoder.setName(layerGroupName);
        for (String layer : layersList) {
            gsLayerGroupEncoder.addLayer(layer);
        }
        return geoServerRESTManager.getPublisher().createLayerGroup(geoServerProperties.getWorkSpaceName(), layerGroupName, gsLayerGroupEncoder);
    }
 /**
     * 調(diào)用geoserver為圖層組配置圖層
     */
    @Override
    public boolean configureLayerGroup(String layerGroupName, ArrayList<String> layersList) {
        GSLayerGroupEncoder gsLayerGroupEncoder = new GSLayerGroupEncoder();
        gsLayerGroupEncoder.setWorkspace(geoServerProperties.getWorkSpaceName());
        gsLayerGroupEncoder.setName(layerGroupName);
        for (String layer : layersList) {
            gsLayerGroupEncoder.addLayer(layer);
        }
        return geoServerRESTManager.getPublisher().configureLayerGroup(geoServerProperties.getWorkSpaceName(), layerGroupName, gsLayerGroupEncoder);
    }

    /**
     * 發(fā)布tif格式圖層
     *
     * @param storeName     目錄名稱/圖層名稱
     * @param fileDirectory tif文件全路徑
     */
    private void publishTiffData(String storeName, String fileDirectory) throws Exception {
        GSImageMosaicEncoder gsCoverageEncoder = new GSImageMosaicEncoder();
         //設(shè)置坐標(biāo)系
        gsCoverageEncoder.setSRS("EPSG:4326");
        gsCoverageEncoder.setName(storeName);
        //todo 去斜拍照片的黑邊,但是會導(dǎo)致圖層發(fā)白,清晰度丟失,暫無解決辦法(樣式?)
        gsCoverageEncoder.setInputTransparentColor("#000000");
        GSLayerEncoder layerEncoder = new GSLayerEncoder();
        RESTCoverageStore publish = geoServerRESTManager.getPublisher().publishExternalGeoTIFF(geoServerProperties.getWorkSpaceName(), storeName, new File(fileDirectory), gsCoverageEncoder, layerEncoder);
        log.info("publishTiffData store:{} fileDirectory:{} publish (TIFF文件發(fā)布狀態(tài)) : {}", storeName, fileDirectory, publish);
    }
}

上面的LayerGroup和Layer實(shí)體類:

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
@TableName(value ="layer_group")
@Data
public class LayerGroup implements Serializable {
    /**
     * 
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 設(shè)備id
     */
    private String deviceId;

    /**
     * 圖層組名稱
     */
    private String layerGroupName;

    /**
     * 圖層組所屬:SAR/CCD
     */
    private String layerGroupAffiliation;
    /**
     * 圖層組bbox
     */
    private Double bboxMinX;
    /**
     * 圖層組bbox
     */
    private Double bboxMinY;

    /**
     * 圖層組bbox
     */
    private Double bboxMaxX;
    /**
     * 圖層組bbox
     */
    private Double bboxMaxY;


    /**
     * 圖層組路徑
     */
    private String layerGroupUrl;

    /**
     * 圖層組存儲目錄
     */
    private String layerGroupDirectory;

    /**
     * 創(chuàng)建時間
     */
    private Date gmtCreate;

    /**
     * 更新時間
     */
    private Date gmtModified;

    /**
     * 創(chuàng)建者id
     */
    private String gmtCreateBy;

    /**
     * 更新者id
     */
    private String gmtModifiedBy;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    /**
     * 圖層組名稱 workspace:name
     */
    private String layer;
}

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

@TableName(value ="layer")
@Data
public class Layer implements Serializable {
    /**
     * 
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 所屬圖層組ID
     */
    private Long layerGroupId;

    /**
     * 設(shè)備id
     */
    private String deviceId;

    /**
     * 圖層名稱
     */
    private String layerName;

    /**
     * 圖層tif圖片文件路徑
     */
    private String layerTifUrl;

    /**
     * 拓展字段
     */
    private String layerExtend;

    /**
     * 創(chuàng)建時間
     */
    private Date gmtCreate;

    /**
     * 更新時間
     */
    private Date gmtModified;

    /**
     * 創(chuàng)建者id
     */
    private String gmtCreateBy;

    /**
     * 更新者id
     */
    private String gmtModifiedBy;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

遇到的坑:
1.拼裝的geoserver預(yù)覽url加載很慢


image.png

這個問題困擾了我整整2天時間,中間各種百度和求助前同事,均無頭緒。
嘗試過加配置,集群,換交換機(jī),甚至修改源碼了,最后才發(fā)現(xiàn)問題出在了mount掛載這一步
原來受限于nas和服務(wù)器間的網(wǎng)線是百兆且是二手機(jī)械盤,導(dǎo)致nas往服務(wù)器copy文件的速度在1Mb/s-10Mb/s,而一張100多Mb的tif圖片光傳輸都要10多秒,所以通過top看geoserver根本的資源消耗根本不大,后來我嘗試直接把文件拷貝到服務(wù)器里請求速度直接起飛到幾毫秒,然后就是更換nas網(wǎng)線為千兆線,硬盤換位ssd后速度達(dá)到幾秒一次

2.原圖發(fā)布斜拍的會有黑邊,setInputTransparentColor后黑邊沒了,但是會導(dǎo)致圖層發(fā)白,清晰度丟失,暫無解決辦法,或許可以通過設(shè)置樣式來改善,但是時間有限就沒有試過了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容