diff --git a/apps/sn/app/map/page.tsx b/apps/sn/app/map/page.tsx new file mode 100644 index 000000000..4da5c0d23 --- /dev/null +++ b/apps/sn/app/map/page.tsx @@ -0,0 +1,36 @@ +'use client' + +import Gis from '@common/business_components/ui/map/Gis'; +import { SearchDiv, SelectBox, UiTable, MainDiv, Title, ContentsDiv, SearchFormLeft, SearchFormRight } from '@common/business_components'; +import ListDiv from '@common/business_components/ui/list_div'; +import { Button, Input } from '@common/components'; +import { useEffect, useState } from "react"; +import { mapService } from "@service"; + +const Map = () => { + + const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 + + + const fetchData = async () => { + try { + const response = await mapService.getLyrStngs(); + setTreeData(response.data); // API 응답 저장 + } catch (error) { + console.error("데이터 불러오기 실패:", error); + } + }; + + // 컴포넌트 마운트 시 1회 실행 + useEffect(() => { + fetchData(); + }, []); + + return ( +
+ +
+ ); +}; + +export default Map; diff --git a/apps/sn/app/oper/pplines/page.tsx b/apps/sn/app/oper/pplines/page.tsx index b5b64b003..48621bddc 100644 --- a/apps/sn/app/oper/pplines/page.tsx +++ b/apps/sn/app/oper/pplines/page.tsx @@ -1,5 +1,5 @@ 'use client' - +import "@common/assets/styles/grid.css"; import { useEffect, useState } from "react"; import { SearchDiv, SelectBox, UiTable, MainDiv, Title, ContentsDiv, SearchFormLeft, SearchFormRight } from '@common/business_components'; import ListDiv from '@common/business_components/ui/list_div'; diff --git a/apps/sn/app/service/index.ts b/apps/sn/app/service/index.ts index 5052b7321..fda21d232 100644 --- a/apps/sn/app/service/index.ts +++ b/apps/sn/app/service/index.ts @@ -1 +1,2 @@ export * from './ppline' +export * from './map' diff --git a/apps/sn/app/service/map.ts b/apps/sn/app/service/map.ts new file mode 100644 index 000000000..062cfeedb --- /dev/null +++ b/apps/sn/app/service/map.ts @@ -0,0 +1,37 @@ + +import axios from 'axios' + +/** + * 관망도 API + */ +const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map` + +export type LyrStngProps = { + lyr_sn: string + lyr_nm: string + upper_lyr_sn: string + drawOrdr: string + flterSql: string + gobjTy: string + lyrTpNm: string + useYn: string + minViewSize: string + iconNm: string + lblAtrbNm: string + fontNm: string + fontIntnColor: string + fontExtnColor: string + fontExtnWdth: string + lnWdth: string + lnColor: string + polgnColor: string + initEprssYn: string + clickPsblYn: string +} + +export const mapService = { + //레이어 설정 조회 + getLyrStngs: () => { + return axios.get(`${API_URL}/lyrStngs`) + } +} diff --git a/apps/sn/app/service/ppline.ts b/apps/sn/app/service/ppline.ts index 7bc7b4408..33e4c9871 100644 --- a/apps/sn/app/service/ppline.ts +++ b/apps/sn/app/service/ppline.ts @@ -2,10 +2,9 @@ import axios from 'axios' /** - * 하수관로 API URL + * 하수관로 API */ -const API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL -const PPLINES_URL = `${API_URL}/v1/pplines` +const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/pplines` const PAGE_NO = 1 const PAGE_SIZE = 20 @@ -16,23 +15,25 @@ export type PplineProps = { sttyEmd: string } -/** - * 하수관로 서비스 - */ export const pplinesService = { + //목록 getPplines: (size: number = PAGE_SIZE, page: number = PAGE_NO) => { - return axios.get(`${PPLINES_URL}`) + return axios.get(`${API_URL}`) }, + //상세 getPpline: (swageLnkgPplPk: string) => { - return axios.get(`${PPLINES_URL}/${swageLnkgPplPk}`) + return axios.get(`${API_URL}/${swageLnkgPplPk}`) }, + //저장 savePpline: (swageLnkgPplPk: string) => { - return axios.post(`${PPLINES_URL}`) + return axios.post(`${API_URL}`) }, + //수정 updatePpline: (swageLnkgPplPk: string) => { - return axios.put(`${PPLINES_URL}/${swageLnkgPplPk}`) + return axios.put(`${API_URL}/${swageLnkgPplPk}`) }, + //삭제 removePpline: (swageLnkgPplPk: string) => { - return axios.delete(`${PPLINES_URL}/${swageLnkgPplPk}`) + return axios.delete(`${API_URL}/${swageLnkgPplPk}`) } } diff --git a/apps/sn/next.config.ts b/apps/sn/next.config.ts index e181f70cf..bc9505fef 100644 --- a/apps/sn/next.config.ts +++ b/apps/sn/next.config.ts @@ -1,6 +1,8 @@ import type { NextConfig } from "next"; import webpack from "webpack"; +const geoserverUrl = process.env.NEXT_PUBLIC_GEOSERVER_URL?.replace(/\/$/, '') ?? ''; + const nextConfig: NextConfig = { basePath: "/sn", typescript: { @@ -40,6 +42,14 @@ const nextConfig: NextConfig = { return config; }, + async rewrites() { + return [ + { + source: '/geoserver/:hasudo*', + destination: `${geoserverUrl}/:hasudo*`, + }, + ]; + }, }; export default nextConfig; diff --git a/apps/sn/package.json b/apps/sn/package.json index ddae226e0..29f630648 100644 --- a/apps/sn/package.json +++ b/apps/sn/package.json @@ -27,7 +27,10 @@ "react-hook-form": "^7.54.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.24.2", - "zustand": "^5.0.3" + "zustand": "^5.0.3", + "ol": "^7.3.0", + "ol-ext": "^4.0.11", + "proj4": "^2.9.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/apps/sn/public/images/map/layericon/BML_CBAS_PS.png b/apps/sn/public/images/map/layericon/BML_CBAS_PS.png new file mode 100644 index 000000000..5ff4add3e Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_CBAS_PS.png differ diff --git a/apps/sn/public/images/map/layericon/BML_ELEV_PS.png b/apps/sn/public/images/map/layericon/BML_ELEV_PS.png new file mode 100644 index 000000000..83970a331 Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_ELEV_PS.png differ diff --git a/apps/sn/public/images/map/layericon/BML_ETCP_PS.png b/apps/sn/public/images/map/layericon/BML_ETCP_PS.png new file mode 100644 index 000000000..df9e4957b Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_ETCP_PS.png differ diff --git a/apps/sn/public/images/map/layericon/BML_STMS_PS.png b/apps/sn/public/images/map/layericon/BML_STMS_PS.png new file mode 100644 index 000000000..423f2e8fc Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_STMS_PS.png differ diff --git a/apps/sn/public/images/map/layericon/BML_SYMB_PS.png b/apps/sn/public/images/map/layericon/BML_SYMB_PS.png new file mode 100644 index 000000000..497589bea Binary files /dev/null and b/apps/sn/public/images/map/layericon/BML_SYMB_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_BYST_PS.png b/apps/sn/public/images/map/layericon/RDL_BYST_PS.png new file mode 100644 index 000000000..1e78cba68 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_BYST_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_CROS_PS.png b/apps/sn/public/images/map/layericon/RDL_CROS_PS.png new file mode 100644 index 000000000..57b5f2ca5 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_CROS_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_ETCT_PS.png b/apps/sn/public/images/map/layericon/RDL_ETCT_PS.png new file mode 100644 index 000000000..29c0b77b4 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_ETCT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_OCUP_PS.png b/apps/sn/public/images/map/layericon/RDL_OCUP_PS.png new file mode 100644 index 000000000..8a1ac0074 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_OCUP_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_RDSN_PS.png b/apps/sn/public/images/map/layericon/RDL_RDSN_PS.png new file mode 100644 index 000000000..5629de9fb Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_RDSN_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_SCLT_PS.png b/apps/sn/public/images/map/layericon/RDL_SCLT_PS.png new file mode 100644 index 000000000..7281dc61d Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_SCLT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_SEPC_PS.png b/apps/sn/public/images/map/layericon/RDL_SEPC_PS.png new file mode 100644 index 000000000..087b4e941 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_SEPC_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_STAT_PS.png b/apps/sn/public/images/map/layericon/RDL_STAT_PS.png new file mode 100644 index 000000000..13d8ea689 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_STAT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_STLT_PS.png b/apps/sn/public/images/map/layericon/RDL_STLT_PS.png new file mode 100644 index 000000000..489524b5c Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_STLT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TFSN_PS.png b/apps/sn/public/images/map/layericon/RDL_TFSN_PS.png new file mode 100644 index 000000000..6dfff6696 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TFSN_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TRCT_PS.png b/apps/sn/public/images/map/layericon/RDL_TRCT_PS.png new file mode 100644 index 000000000..ed1a72d8a Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TRCT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TREE_PS.png b/apps/sn/public/images/map/layericon/RDL_TREE_PS.png new file mode 100644 index 000000000..0ee84dedc Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TREE_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TRSN_PS.png b/apps/sn/public/images/map/layericon/RDL_TRSN_PS.png new file mode 100644 index 000000000..15675cb31 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TRSN_PS.png differ diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png new file mode 100644 index 000000000..ffc225159 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_CLAY_PS.png b/apps/sn/public/images/map/layericon/SWL_CLAY_PS.png new file mode 100644 index 000000000..e603ffa9c Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_CLAY_PS.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_CLAY_PS_RED.png b/apps/sn/public/images/map/layericon/SWL_CLAY_PS_RED.png new file mode 100644 index 000000000..0e5450688 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_CLAY_PS_RED.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_DEPT_PS.png b/apps/sn/public/images/map/layericon/SWL_DEPT_PS.png new file mode 100644 index 000000000..fede6c304 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_DEPT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS.png new file mode 100644 index 000000000..75217f494 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLACK.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLACK.png new file mode 100644 index 000000000..5d2e8a70a Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLACK.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLUE.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLUE.png new file mode 100644 index 000000000..e521cb84a Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_BLUE.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_GRAY.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GRAY.png new file mode 100644 index 000000000..e19f3d8b3 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GRAY.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_GREEN.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GREEN.png new file mode 100644 index 000000000..4503c13c6 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_GREEN.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_RED.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_RED.png new file mode 100644 index 000000000..b8fa45f9c Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_RED.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_MANH_PS_YELLOW.png b/apps/sn/public/images/map/layericon/SWL_MANH_PS_YELLOW.png new file mode 100644 index 000000000..b7de1fbfd Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_MANH_PS_YELLOW.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPEW_PS.png b/apps/sn/public/images/map/layericon/SWL_SPEW_PS.png new file mode 100644 index 000000000..d260ea3a6 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPEW_PS.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS.png new file mode 100644 index 000000000..84c7992e4 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLACK.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLACK.png new file mode 100644 index 000000000..fe67f8f98 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLACK.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLUE.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLUE.png new file mode 100644 index 000000000..691f2f45e Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_BLUE.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREEN.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREEN.png new file mode 100644 index 000000000..d09c4bd97 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREEN.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREY.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREY.png new file mode 100644 index 000000000..32d0bf519 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_GREY.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_SPOT_PS_RED.png b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_RED.png new file mode 100644 index 000000000..a2da36f9b Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_SPOT_PS_RED.png differ diff --git a/apps/sn/public/images/map/layericon/SWL_VENT_PS.png b/apps/sn/public/images/map/layericon/SWL_VENT_PS.png new file mode 100644 index 000000000..834eeec21 Binary files /dev/null and b/apps/sn/public/images/map/layericon/SWL_VENT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPBD_ENTRC.png b/apps/sn/public/images/map/layericon/TL_SPBD_ENTRC.png new file mode 100644 index 000000000..8e7de8593 Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPBD_ENTRC.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPGF_LOC.png b/apps/sn/public/images/map/layericon/TL_SPGF_LOC.png new file mode 100644 index 000000000..74b0adac3 Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPGF_LOC.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPOT_NOIN.png b/apps/sn/public/images/map/layericon/TL_SPOT_NOIN.png new file mode 100644 index 000000000..4800beaeb Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPOT_NOIN.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_BANO.png b/apps/sn/public/images/map/layericon/TL_SPRD_BANO.png new file mode 100644 index 000000000..8d08dca29 Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_BANO.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_CRSRD.png b/apps/sn/public/images/map/layericon/TL_SPRD_CRSRD.png new file mode 100644 index 000000000..57b5f2ca5 Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_CRSRD.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_NONM.png b/apps/sn/public/images/map/layericon/TL_SPRD_NONM.png new file mode 100644 index 000000000..e68c05ab6 Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_NONM.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_RENM.png b/apps/sn/public/images/map/layericon/TL_SPRD_RENM.png new file mode 100644 index 000000000..4800beaeb Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_RENM.png differ diff --git a/apps/sn/public/images/map/layericon/TL_SPRD_RWNM.png b/apps/sn/public/images/map/layericon/TL_SPRD_RWNM.png new file mode 100644 index 000000000..e68c05ab6 Binary files /dev/null and b/apps/sn/public/images/map/layericon/TL_SPRD_RWNM.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_BMAN_PS.png b/apps/sn/public/images/map/layericon/UFL_BMAN_PS.png new file mode 100644 index 000000000..f2f64a641 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BMAN_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_BOPC_PS.png b/apps/sn/public/images/map/layericon/UFL_BOPC_PS.png new file mode 100644 index 000000000..0f8e99f45 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BOPC_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_BPIP_PS.png b/apps/sn/public/images/map/layericon/UFL_BPIP_PS.png new file mode 100644 index 000000000..dcc3622a7 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BPIP_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_BPOL_PS.png b/apps/sn/public/images/map/layericon/UFL_BPOL_PS.png new file mode 100644 index 000000000..4f1102989 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BPOL_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_BTRS_PS.png b/apps/sn/public/images/map/layericon/UFL_BTRS_PS.png new file mode 100644 index 000000000..557c17089 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_BTRS_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_GMNT_PS.png b/apps/sn/public/images/map/layericon/UFL_GMNT_PS.png new file mode 100644 index 000000000..6081d8b00 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GMNT_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_GPIP_PS.png b/apps/sn/public/images/map/layericon/UFL_GPIP_PS.png new file mode 100644 index 000000000..5d7e86fd2 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GPIP_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_GPRS_PS.png b/apps/sn/public/images/map/layericon/UFL_GPRS_PS.png new file mode 100644 index 000000000..79c120f20 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GPRS_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_GVAL_PS.png b/apps/sn/public/images/map/layericon/UFL_GVAL_PS.png new file mode 100644 index 000000000..e8f7cde2e Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_GVAL_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_KMAN_PS.png b/apps/sn/public/images/map/layericon/UFL_KMAN_PS.png new file mode 100644 index 000000000..764ce8ba5 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_KMAN_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_KPOL_PS.png b/apps/sn/public/images/map/layericon/UFL_KPOL_PS.png new file mode 100644 index 000000000..d9fa712cf Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_KPOL_PS.png differ diff --git a/apps/sn/public/images/map/layericon/UFL_VALV_PS.png b/apps/sn/public/images/map/layericon/UFL_VALV_PS.png new file mode 100644 index 000000000..1b5d70178 Binary files /dev/null and b/apps/sn/public/images/map/layericon/UFL_VALV_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_FIRE_PS.png b/apps/sn/public/images/map/layericon/WTL_FIRE_PS.png new file mode 100644 index 000000000..4137a3f74 Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_FIRE_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_FLOW_PS.png b/apps/sn/public/images/map/layericon/WTL_FLOW_PS.png new file mode 100644 index 000000000..285f7edfa Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_FLOW_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_MANH_PS.png b/apps/sn/public/images/map/layericon/WTL_MANH_PS.png new file mode 100644 index 000000000..5f2a57cfe Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_MANH_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_META_PS.png b/apps/sn/public/images/map/layericon/WTL_META_PS.png new file mode 100644 index 000000000..22c7af31b Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_META_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_PIPE_PS.png b/apps/sn/public/images/map/layericon/WTL_PIPE_PS.png new file mode 100644 index 000000000..c9c6863f6 Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_PIPE_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_RSRV_PS.png b/apps/sn/public/images/map/layericon/WTL_RSRV_PS.png new file mode 100644 index 000000000..d7b307a61 Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_RSRV_PS.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_RSRV_PS_GRAY.png b/apps/sn/public/images/map/layericon/WTL_RSRV_PS_GRAY.png new file mode 100644 index 000000000..33a667a1e Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_RSRV_PS_GRAY.png differ diff --git a/apps/sn/public/images/map/layericon/WTL_VALV_PS.png b/apps/sn/public/images/map/layericon/WTL_VALV_PS.png new file mode 100644 index 000000000..ca2b4788b Binary files /dev/null and b/apps/sn/public/images/map/layericon/WTL_VALV_PS.png differ diff --git a/apps/sn/public/images/map/layericon/default.png b/apps/sn/public/images/map/layericon/default.png new file mode 100644 index 000000000..92bf1f587 Binary files /dev/null and b/apps/sn/public/images/map/layericon/default.png differ diff --git a/apps/sn/public/images/map/layericon/reset.png b/apps/sn/public/images/map/layericon/reset.png new file mode 100644 index 000000000..45f889206 Binary files /dev/null and b/apps/sn/public/images/map/layericon/reset.png differ diff --git a/apps/sn/public/images/map/toolbar/arrow_down.png b/apps/sn/public/images/map/toolbar/arrow_down.png new file mode 100644 index 000000000..88590ecaf Binary files /dev/null and b/apps/sn/public/images/map/toolbar/arrow_down.png differ diff --git a/apps/sn/public/images/map/toolbar/arrow_right.png b/apps/sn/public/images/map/toolbar/arrow_right.png new file mode 100644 index 000000000..7090c37dd Binary files /dev/null and b/apps/sn/public/images/map/toolbar/arrow_right.png differ diff --git a/apps/sn/public/images/map/toolbar/backward_icon.png b/apps/sn/public/images/map/toolbar/backward_icon.png new file mode 100644 index 000000000..3a853475e Binary files /dev/null and b/apps/sn/public/images/map/toolbar/backward_icon.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_area.png b/apps/sn/public/images/map/toolbar/btn_cont_area.png new file mode 100644 index 000000000..044562b55 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_area.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_area_on.png b/apps/sn/public/images/map/toolbar/btn_cont_area_on.png new file mode 100644 index 000000000..0129ff1bd Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_area_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_next.png b/apps/sn/public/images/map/toolbar/btn_cont_next.png new file mode 100644 index 000000000..075111dcf Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_next.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_next_on.png b/apps/sn/public/images/map/toolbar/btn_cont_next_on.png new file mode 100644 index 000000000..4fdbe3a00 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_next_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_prev.png b/apps/sn/public/images/map/toolbar/btn_cont_prev.png new file mode 100644 index 000000000..7e3fae7d2 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_prev.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_prev_on.png b/apps/sn/public/images/map/toolbar/btn_cont_prev_on.png new file mode 100644 index 000000000..fb2d55cfd Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_prev_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_print.png b/apps/sn/public/images/map/toolbar/btn_cont_print.png new file mode 100644 index 000000000..82596b83d Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_print.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_print_on.png b/apps/sn/public/images/map/toolbar/btn_cont_print_on.png new file mode 100644 index 000000000..bb7f27334 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_print_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_ruler.png b/apps/sn/public/images/map/toolbar/btn_cont_ruler.png new file mode 100644 index 000000000..501e460a4 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_ruler.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_ruler_on.png b/apps/sn/public/images/map/toolbar/btn_cont_ruler_on.png new file mode 100644 index 000000000..676a81bcc Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_ruler_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_save.png b/apps/sn/public/images/map/toolbar/btn_cont_save.png new file mode 100644 index 000000000..c057b9377 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_save.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_save_on.png b/apps/sn/public/images/map/toolbar/btn_cont_save_on.png new file mode 100644 index 000000000..bf8e6d623 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_save_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_sketch.png b/apps/sn/public/images/map/toolbar/btn_cont_sketch.png new file mode 100644 index 000000000..38a5f507f Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_sketch.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_sketch_on.png b/apps/sn/public/images/map/toolbar/btn_cont_sketch_on.png new file mode 100644 index 000000000..79fb54e03 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_sketch_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomin.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomin.png new file mode 100644 index 000000000..ac32fbab0 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomin.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomin_on.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomin_on.png new file mode 100644 index 000000000..f258a965f Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomin_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomout.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomout.png new file mode 100644 index 000000000..2ee0b5161 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomout.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_cont_zoomout_on.png b/apps/sn/public/images/map/toolbar/btn_cont_zoomout_on.png new file mode 100644 index 000000000..58dcbfdae Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_cont_zoomout_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_add.png b/apps/sn/public/images/map/toolbar/btn_edit_add.png new file mode 100644 index 000000000..fe4f0993f Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_add.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_add_on.png b/apps/sn/public/images/map/toolbar/btn_edit_add_on.png new file mode 100644 index 000000000..37a836298 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_add_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_del.png b/apps/sn/public/images/map/toolbar/btn_edit_del.png new file mode 100644 index 000000000..3bb372ab9 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_del.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_del_on.png b/apps/sn/public/images/map/toolbar/btn_edit_del_on.png new file mode 100644 index 000000000..89e558739 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_del_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_next.png b/apps/sn/public/images/map/toolbar/btn_edit_next.png new file mode 100644 index 000000000..df4913c90 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_next.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_next_on.png b/apps/sn/public/images/map/toolbar/btn_edit_next_on.png new file mode 100644 index 000000000..4e5540b55 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_next_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_prev.png b/apps/sn/public/images/map/toolbar/btn_edit_prev.png new file mode 100644 index 000000000..e94bf6d42 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_prev.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_prev_on.png b/apps/sn/public/images/map/toolbar/btn_edit_prev_on.png new file mode 100644 index 000000000..8645dedfa Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_prev_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_save.png b/apps/sn/public/images/map/toolbar/btn_edit_save.png new file mode 100644 index 000000000..5f01dd55c Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_save.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_save_on.png b/apps/sn/public/images/map/toolbar/btn_edit_save_on.png new file mode 100644 index 000000000..b035687d6 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_save_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_slt.png b/apps/sn/public/images/map/toolbar/btn_edit_slt.png new file mode 100644 index 000000000..aaf2bbd08 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_slt.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_slt_on.png b/apps/sn/public/images/map/toolbar/btn_edit_slt_on.png new file mode 100644 index 000000000..02d035a74 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_slt_on.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_snap.png b/apps/sn/public/images/map/toolbar/btn_edit_snap.png new file mode 100644 index 000000000..fc91d4304 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_snap.png differ diff --git a/apps/sn/public/images/map/toolbar/btn_edit_snap_on.png b/apps/sn/public/images/map/toolbar/btn_edit_snap_on.png new file mode 100644 index 000000000..f856d8fbe Binary files /dev/null and b/apps/sn/public/images/map/toolbar/btn_edit_snap_on.png differ diff --git a/apps/sn/public/images/map/toolbar/close_icon.png b/apps/sn/public/images/map/toolbar/close_icon.png new file mode 100644 index 000000000..e8e76d5d7 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/close_icon.png differ diff --git a/apps/sn/public/images/map/toolbar/ico_gis_sprite.png b/apps/sn/public/images/map/toolbar/ico_gis_sprite.png new file mode 100644 index 000000000..0ee4b5985 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/ico_gis_sprite.png differ diff --git a/apps/sn/public/images/map/toolbar/icon-viewclose.png b/apps/sn/public/images/map/toolbar/icon-viewclose.png new file mode 100644 index 000000000..d93ce9c05 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/icon-viewclose.png differ diff --git a/apps/sn/public/images/map/toolbar/img_base_map.png b/apps/sn/public/images/map/toolbar/img_base_map.png new file mode 100644 index 000000000..7f96fcf5e Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_base_map.png differ diff --git a/apps/sn/public/images/map/toolbar/img_network_map.png b/apps/sn/public/images/map/toolbar/img_network_map.png new file mode 100644 index 000000000..df80af6dc Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_network_map.png differ diff --git a/apps/sn/public/images/map/toolbar/img_road_map.png b/apps/sn/public/images/map/toolbar/img_road_map.png new file mode 100644 index 000000000..02d015d12 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_road_map.png differ diff --git a/apps/sn/public/images/map/toolbar/img_settle_map.png b/apps/sn/public/images/map/toolbar/img_settle_map.png new file mode 100644 index 000000000..1a93f50e8 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/img_settle_map.png differ diff --git a/apps/sn/public/images/map/toolbar/loading.gif b/apps/sn/public/images/map/toolbar/loading.gif new file mode 100644 index 000000000..7c3683bb4 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/loading.gif differ diff --git a/apps/sn/public/images/map/toolbar/map_pin.png b/apps/sn/public/images/map/toolbar/map_pin.png new file mode 100644 index 000000000..b6cc2f6aa Binary files /dev/null and b/apps/sn/public/images/map/toolbar/map_pin.png differ diff --git a/apps/sn/public/images/map/toolbar/toggle_off.png b/apps/sn/public/images/map/toolbar/toggle_off.png new file mode 100644 index 000000000..d5dea9f67 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/toggle_off.png differ diff --git a/apps/sn/public/images/map/toolbar/toggle_on.png b/apps/sn/public/images/map/toolbar/toggle_on.png new file mode 100644 index 000000000..9cb573875 Binary files /dev/null and b/apps/sn/public/images/map/toolbar/toggle_on.png differ diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js new file mode 100644 index 000000000..d0fbefe2a --- /dev/null +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -0,0 +1,159 @@ +import './lib/css/gis.common.css'; +import React, { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@common/components/ui/tabs'; +import OlMap from 'ol/Map'; +import View from 'ol/View'; +import { defaults as defaultControls, OverviewMap } from 'ol/control'; +import { defaults } from 'ol/interaction'; +import * as olProj from 'ol/proj'; +import proj4 from 'proj4'; +import { register } from 'ol/proj/proj4'; +import GisApp from './lib/gis.core'; +import GisContent from './GisContent'; +import GisTree from './GisTree'; +import GisLonLat from './GisLonLat'; +import GisToolBar from './GisToolBar'; +import GisSearch from './GisSearch'; +import MapToggleBtn from './MapToggleBtn'; + +export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null, checkedLayerIds:null}); +export const useGisContext = () => useContext(GisContext); + +function Gis({ treeData = [] }) { + const mapRef = useRef(); + const overviewMapRef = useRef(null); + const [mapObject, setMapObject] = useState({}); + const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); + //const [treeData, setTreeData] = useState([]); + const checkedLayerIds = useRef([]); + let gisMap = null; + + useEffect(() => { + if (!mapRef.current) return; + + // EPSG:5179 등록 + proj4.defs([ + [ + 'EPSG:5179', + '+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs', + ], + ]); + register(proj4); + + //맵 생성성 + const map = new OlMap({ + layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], + renderer: 'webgl', + target: mapRef.current, + interactions: defaults({ doubleClickZoom: false }), + controls: defaultControls({ + zoom: false, + rotate: false, + attributionOptions: { + collapsible: false, + }, + }).extend([]), + view: new View({ + projection: GisApp.TO_PROJ, + zoom: gisCore.defaults.zoom, + center: olProj.transform( + [126.85306, 35.571634], + GisApp.FROM_PROJ, + GisApp.TO_PROJ + ), + minZoom: gisCore.defaults.minZoom, + maxZoom: gisCore.defaults.maxZoom, + }), + }); + + //미니맵 + const overviewMap = new OverviewMap({ + layers: [GisApp.Module.core.baseMiniMapLayers['base']], + view: new View({ + projection: GisApp.TO_PROJ, + minZoom: 9, + }), + collapsed: false, + collapseLabel: '\u00BB', + label: '\u00AB', + }); + + map.addControl(overviewMap); + gisCore.__proto__.map = map; + + gisCore.scaleControl(); + gisCore.changeBaseLayer('base'); + setMapObject({ map: map }); + }, []); + + useEffect(() => { + //if (!locgovCd) return null; + if (!treeData || treeData.length === 0) return; + + gisCore.deleteAllOverlay(); + gisCore.deleteAllLayer(); + + const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); + GisApp.layerCode = layerCode; + + //setTreeData(data); + const layerItems = getLayerItems(layerCode); + //setEditLayerItems(prev => [...layerItems]); + }, [treeData]); + + const getLayerItems= (layerCode) => { + const layerItems = Object.entries(layerCode) + .filter(([key, obj], idx) => obj?.updtPsblYn === 'Y') + .map(([key, obj]) => ({ + ...obj, + name: obj.lyrNm, + label: obj.lyrNm, + value: obj.lyrSn + '', + })) + .sort((a, b) => { + if (a.sortOrdr > b.sortOrdr) return 1; + if (a.sortOrdr < b.sortOrdr) return -1; + return 0; + }); + return layerItems; + }; + + if (mapObject.map) { + try { + gisMap = mapObject.map; + } catch (e) { + console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 + } + } + + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + + return ( + + <> +
+
+ + + 레이어 + 시설검색 + + + + + + + + +
+
+ + + +
+ + + ); +} + +export default React.memo(Gis); diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js new file mode 100644 index 000000000..2179a53d8 --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisContent.js @@ -0,0 +1,61 @@ +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import GisApp from './lib/gis.core'; +import { useGisContext } from './Gis'; +import GisLonLat from './GisLonLat'; +import GisSelect from './GisSelect'; +import GisToolBar from './GisToolBar'; +import './lib/css/gis.edit.css'; +import MapToggleBtn from './MapToggleBtn'; +//import GisEdit from './GisEdit'; + +export default function GisContent({ edit = false }) { + const { gisMap, gisCore } = useGisContext(); + const [visibleComponents, setVisibleComponents] = useState({}); + const addressResultLayer = useRef(null); + const [playShow, setPlayShow] = useState({ show: false }); + + //셀렉트 버튼 이벤트 + const clickFeature = useCallback(feature => { + if (feature) { + const layer = feature.getLayer(); + setVisibleComponents(prevState => ({ + ...prevState, + })); + } + }, []); + + const selectRslt = useCallback(feature => { + GisApp.Module.layer.highLightFeature( + [feature], + { + id: GisApp.hlLayerId, + zIndex: 10, + opacity: 1, + selectable: true, + }, + { width: 30 } + ); + clickFeature(feature); + }, []); + + useEffect(() => { + if (gisMap) { + setVisibleComponents(prevState => ({ + ...prevState, + })); + } + }, [gisMap]); + + if (!gisMap) return null; + + return ( + <> + {/**/} + {/* */} + + + + {/* {edit && } */} + + ); +} diff --git a/packages/common/business_components/src/ui/map/GisEdit.js b/packages/common/business_components/src/ui/map/GisEdit.js new file mode 100644 index 000000000..7c961c865 --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisEdit.js @@ -0,0 +1,173 @@ +import GisApp from './lib/gis.core'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { GisContext, setTree } from './Gis'; +import Button from '../button/Button'; +import { gAlert, gConfirm } from './lib/util'; +import { fclApi } from '../../js/apis/fcl/fclApi'; + +export default React.memo(function GisEdit() { + const { editLayerItems = [], siteSn, locgovCd, checkedLayerIds } = useContext(GisContext); + const editModule = GisApp.Module.edit; + const ftrIdn = useRef(null); + const selectedLyrId = useRef(null); + if (editLayerItems.length > 0 && !selectedLyrId.current) { + selectedLyrId.current = editLayerItems[0].value; + } + const [btnEvt, setBtnEvt] = useState({ edit: false, modify: false, add: false, snap: false }); + const edit = () => { + const edit = !btnEvt.edit; + if (edit) { + setBtnEvt({ ...btnEvt, edit: edit }); + } else { + setBtnEvt({ edit: edit, modify: false, add: false, snap: false }); + editModule.clearUndoRedoFeature(); + } + editModule.removeInteraction(); + }; + + const modify = () => { + const modify = !btnEvt.modify; + setBtnEvt({ ...btnEvt, ...{ add: false }, modify }); + if (modify) { + editModule.setModify(); + } else { + editModule.removeModify(); + } + }; + + const add = () => { + let add = !btnEvt.add; + if (add) { + if (!checkedLayerIds?.current?.includes(selectedLyrId.current)) { + alert('트리에서 레이어 체크 후 추가 가능합니다.'); + return; + } + const errMsg = editModule.setDraw(selectedLyrId.current); + if (errMsg) { + alert(errMsg); + return; + } + } else { + editModule.removeDraw(); + } + setBtnEvt({ ...btnEvt, ...{ modify: false }, add }); + }; + + const del = () => { + editModule.deleteFeature(); + }; + + const undo = () => { + GisApp.Module.edit.undo(); + }; + + const redo = () => { + GisApp.Module.edit.redo(); + }; + + const snap = () => { + const snap = !btnEvt.snap; + setBtnEvt({ ...btnEvt, snap }); + if (snap) { + editModule.setSnap(); + } else { + editModule.removeSnap(); + } + }; + + const validateEditFeatures = editFeatures => { + if (editFeatures.length === 0) { + //gAlert('INFO', 'NO_CHANGED_DATA'); + return false; + } + return true; + }; + + const save = async () => { + const editFeatureInfo = editModule.getEditFeatureInfo(); + const editFeatures = Object.values(editFeatureInfo).map(value => ({ + siteSn, + locgovCd, + geom: value.geom, + flag: value.flag, + lyrId: value.lyr_id, + })); + if (!validateEditFeatures(editFeatures)) { + return; + } + if (!gConfirm('CONFIRM', 'SAVE_QUEST')) return false; + const result = await fclApi.saveEditFeatures(editFeatures); + if (result.code === 200) { + editModule.clearUndoRedo(); + } + }; + + useEffect(() => { + //setBtnEvt({ edit: false, modify: false, add: false, snap: false }); + editModule.setUndoInteraction(); + editModule.removeInteraction(); + }, [locgovCd]); + if (editLayerItems.length === 0) return; + return ( + <> +
+ + + + {btnEvt.edit && ( +
    +
  • +
    + 수정 +
    +
  • +
  • +
    + 추가 +
    +
  • +
  • +
    + 삭제 +
    +
  • +
  • +
    + 이전 +
    +
  • +
  • +
    + 다음 +
    +
  • +
  • +
    + 스냅 +
    +
  • +
  • +
    + 저장 +
    +
  • +
+ )} +
+ + ); +}); diff --git a/packages/common/business_components/src/ui/map/GisLonLat.js b/packages/common/business_components/src/ui/map/GisLonLat.js new file mode 100644 index 000000000..2cee2a9c8 --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisLonLat.js @@ -0,0 +1,44 @@ +import React from 'react'; +import GisApp from './lib/gis.core'; +import { MousePosition } from 'ol/control'; +import { useContext, useEffect, useRef } from 'react'; +import { GisContext } from './Gis'; +export default React.memo(function GisLonLat() { + const { gisCore } = useContext(GisContext); + const labelLonLatRef = useRef(null); + + useEffect(() => { + if (!gisCore?.map || !labelLonLatRef.current) return; + + // 기존 MousePosition 제거를 위한 컨트롤 추적 + let existingMouseControls = gisCore.map + .getControls() + .getArray() + .filter(control => control instanceof MousePosition); + + // 기존 MousePosition 컨트롤 제거 + existingMouseControls.forEach(control => { + gisCore.map.removeControl(control); + }); + + // 새로운 MousePosition 컨트롤 추가 + const mousePosition = new MousePosition({ + coordinateFormat: function (coordinate) { + let lng = coordinate[0]; + let lat = coordinate[1]; + return `경도 : ${lng.toFixed(3)}   위도 : ${lat.toFixed(3)}`; + }, + projection: GisApp.FROM_PROJ, + className: 'custom-mouse-position', + target: labelLonLatRef.current, + }); + gisCore.map.addControl(mousePosition); + }, [gisCore.map]); + return ( + <> +
    +
  • +
+ + ); +}); diff --git a/packages/common/business_components/src/ui/map/GisSearch.js b/packages/common/business_components/src/ui/map/GisSearch.js new file mode 100644 index 000000000..c321d8efa --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisSearch.js @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function GisSearch() { + return ( +
+

범례 영역입니다 (향후 컴포넌트로 교체 가능)

+
+ ); +} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/map/GisSelect.js b/packages/common/business_components/src/ui/map/GisSelect.js new file mode 100644 index 000000000..85354d8b5 --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisSelect.js @@ -0,0 +1,36 @@ +import React from 'react'; +import GisApp from './lib/gis.core'; +import GisOlSelect from './lib/gis.select'; +import { useContext } from 'react'; +import { GisContext } from './Gis'; +export default React.memo(function GisSelect({ clickFeature }) { + const { gisMap } = useContext(GisContext); + const select = new GisOlSelect(gisMap); + GisApp.Module.select = select; + const reset = () => { + select.reset(); + }; + + select.getSelectedInfo(function (features) { + clickFeature(features[0]); + }); + return ( + <> +
+

선,면,다각형을 이용한 시설물 선택 영역

+
    +
  • +
    + 단일선택 +
    +
  • +
  • +
    + 새로고침 +
    +
  • +
+
+ + ); +}); diff --git a/packages/common/business_components/src/ui/map/GisToolBar.js b/packages/common/business_components/src/ui/map/GisToolBar.js new file mode 100644 index 000000000..c5c613cc9 --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisToolBar.js @@ -0,0 +1,71 @@ +import React from 'react'; +import GisApp from './lib/gis.core'; +import GisOlMeasure from './lib/gis.measure'; +import { useContext } from 'react'; +import { useGisContext } from './Gis'; +export default React.memo(function GisToolBar() { + const { gisMap } = useGisContext(); + if (!gisMap) return <>; + const gisMeasuer = new GisOlMeasure(gisMap); + GisApp.Module.measure = gisMeasuer; + const zoomIn = () => { + if (gisMap.getView().getMaxZoom() > gisMap.getView().getZoom()) { + gisMap.getView().animate({ + zoom: gisMap.getView().getZoom() + 1, + duration: 250, + }); + } + }; + const zoomOut = () => { + if (gisMap.getView().getMaxZoom() > gisMap.getView().getZoom()) { + gisMap.getView().animate({ + zoom: gisMap.getView().getZoom() - 1, + duration: 250, + }); + } + }; + + const measure = type => { + gisMeasuer.addInteraction(type); + }; + + const reset = () => { + gisMeasuer.removeFeature(); + }; + return ( + <> +
+
+

toolbar 영역

+
    +
  • +
    + 확대 +
    +
  • +
  • +
    + 축소 +
    +
  • +
  • +
    measure(GisApp.measure.LineString)}> + 거리측정 +
    +
  • +
  • +
    measure(GisApp.measure.Polygon)}> + 면적측정 +
    +
  • +
  • +
    reset()}> + 리셋 +
    +
  • +
+
+
+ + ); +}); diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js new file mode 100644 index 000000000..487e8bfec --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -0,0 +1,234 @@ +import GisApp from './lib/gis.core'; +import { util } from './lib/util'; +import React, { useEffect, useState } from 'react'; +import { useGisContext } from './Gis'; +import { DndProvider } from "react-dnd"; +import { + Tree, + MultiBackend, + getBackendOptions +} from "@minoru/react-dnd-treeview"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components/ui/tabs"; + +export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => {} }) { + const { gisCore, treeData, checkedLayerIds } = useGisContext(); + const [checked, setChecked] = useState([]); + const [expanded, setExpanded] = useState([]); + const [nodes, setNodes] = useState([]); + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + + useEffect(() => { + if (treeData && treeData.length > 0) { + const nodeData = setNodeValues(treeData); + setNodes(nodeData); + gisCore.setAllLayersOnMap(false); + } + console.log(treeData) + }, [treeData]); + + useEffect(() => { + if (nodes.length > 0) { + const defaultChecked = getDefaultCheckedNodes(nodes); + setChecked(defaultChecked); + const defaultExpanded = Array.from(new Set([ + ...defaultChecked, + ...defaultChecked.flatMap((id) => getAncestors(id, nodes)) + ])); + setExpanded(defaultExpanded); + gisCore.setLayerOnMap(defaultChecked, true); + } + console.log(nodes) + }, [nodes]); + + const getDefaultCheckedNodes = (nodes, values = []) => { + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.data.initEprssYn === 'Y') { + values.push(node.data.lyrSn); + checkedLayerIds?.current.push(node.data.lyrSn + ''); + } + } + return values; + }; + + const getDescendants = (id, all) => { + const children = all.filter((n) => n.parent === id); + return children.reduce( + (acc, child) => acc.concat(child.id, ...getDescendants(child.id, all)), + [] + ); + }; + + const getAncestors = (id, all) => { + const node = all.find((n) => n.id === id); + if (!node || node.parent == null) return []; + return [node.parent, ...getAncestors(node.parent, all)]; + }; + + const isEveryChildChecked = (parentId, all, checked) => { + const children = all.filter((n) => n.parent === parentId); + return children.every((c) => checked.includes(c.id)); + }; + + const handleCheck = (id) => { + setChecked((prevChecked) => { + const isChecked = prevChecked.includes(id); + let newChecked = [...prevChecked]; + if (isChecked) { + const descendants = getDescendants(id, nodes); + newChecked = newChecked.filter((cid) => cid !== id && !descendants.includes(cid)); + const ancestors = getAncestors(id, nodes); + ancestors.forEach((p) => { + if (newChecked.includes(p)) { + newChecked = newChecked.filter((cid) => cid !== p); + } + }); + } else { + const descendants = getDescendants(id, nodes); + newChecked = Array.from(new Set([...newChecked, id, ...descendants])); + const ancestors = getAncestors(id, nodes); + ancestors.forEach((p) => { + if (isEveryChildChecked(p, nodes, newChecked)) { + newChecked.push(p); + } + }); + // setExpanded((prev) => Array.from(new Set([...prev, ...ancestors]))); + } + const allIds = nodes.map((n) => n.id); + const unchecked = allIds.filter((nid) => !newChecked.includes(nid)); + checkedLayerIds.current = newChecked; + gisCore.setLayerOnMap(newChecked, true); + gisCore.setLayerOnMap(unchecked, false); + return newChecked; + }); + }; + + const setNodeValues = (result) => { + return result.map((item) => { + const src = makeLegendSrc(item); + let label = item.lyrNm; + return { + id: item.lyrSn, + text: label, + parent: item.upperLyrSn, + droppable: !!item.children, + data: item, + icon: src ? {item.lyrNm} : '', + }; + }); + }; + + const findUpLyrId = (nodes, targetLyrId) => { + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.data && node.data.lyrSn === targetLyrId) { + return node.data.upperLyrSn; + } + if (node.children) { + const upLyrId = findUpLyrId(node.children, targetLyrId); + if (upLyrId) { + return upLyrId; + } + } + } + return null; + }; + + const makeLegendSrc = layerinfo => { + if(!layerinfo.gobjTy) return null; + + let src; + if (layerinfo.iconNm) { + src = GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm; + } else { + const ss = makeLegendImage(layerinfo); + src = ss.src; + } + return src; + }; + + const makeLegendImage = layerinfo => { + let image = document.createElement('img'); + let canvas = document.createElement('canvas'); + canvas.width = 16; + canvas.height = 16; + let ctx = canvas.getContext('2d'); + ctx.beginPath(); + + if (layerinfo.gobjTy === 'lineLabelPoligon') { + ctx.moveTo(1, 1); + ctx.lineTo(15, 1); + ctx.lineTo(15, 15); + ctx.lineTo(1, 15); + ctx.lineTo(1, 1); + } else if (layerinfo.gobjTy === 'line') { + ctx.moveTo(1, 15); + ctx.lineTo(5, 1); + ctx.lineTo(9, 15); + ctx.lineTo(15, 1); + } else { + ctx.moveTo(1, 1); + ctx.lineTo(15, 1); + ctx.lineTo(15, 15); + ctx.lineTo(1, 15); + ctx.lineTo(1, 1); + } + + if (layerinfo.gobjTy === 'lineLabelPoligon') { + ctx.fillStyle = layerinfo.polgnColor; + ctx.fill(); + ctx.lineWidth = 1; + ctx.strokeStyle = layerinfo.lnColor; + ctx.stroke(); + } + + if (layerinfo.gobjTy === 'line') { + ctx.lineWidth = 1; + ctx.strokeStyle = layerinfo.lnColor; + ctx.stroke(); + } + + image.src = canvas.toDataURL('image/png'); + return image; + }; + + if (!treeData || treeData.length === 0) return null; + + return ( + <> + + ( +
+ {node.droppable && ( + + )} + handleCheck(node.id)} + className="cursor-pointer" + /> + {node.icon && {node.icon}} + {node.text} +
+ )} + sort={(a, b) => { + const aOrder = parseInt(a.data?.sortOrdr ?? '0'); + const bOrder = parseInt(b.data?.sortOrdr ?? '0'); + return aOrder - bOrder; + }} + /> +
+ + ); +}); diff --git a/packages/common/business_components/src/ui/map/Google3DMap.js b/packages/common/business_components/src/ui/map/Google3DMap.js new file mode 100644 index 000000000..2ae6547dd --- /dev/null +++ b/packages/common/business_components/src/ui/map/Google3DMap.js @@ -0,0 +1,60 @@ +import React, { useEffect, useRef, useState } from 'react'; +import OLCesium from 'olcs/OLCesium'; +import olView from 'ol/View.js'; +import olSourceImageWMS from 'ol/source/ImageWMS.js'; +import olSourceOSM from 'ol/source/OSM.js'; +import olLayerImage from 'ol/layer/Image.js'; +import olLayerTile from 'ol/layer/Tile.js'; +import olMap from 'ol/Map.js'; +import { GLOBAL } from '../../js/consts/global'; + +const Google3DMap = () => { + const mapRef = useRef(null); + const [ol3d, setOl3d] = useState(null); + + useEffect(() => { + if (!mapRef.current) return; + + const customProjSource = new olSourceImageWMS({ + attributions: '© National parks / geo.admin.ch', + crossOrigin: 'anonymous', + params: { LAYERS: 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung' }, + projection: 'EPSG:21781', + url: 'https://wms.geo.admin.ch/', + }); + + const { Cesium } = window; + Cesium.Ion.defaultAccessToken = GLOBAL.CESIUM_ION_TOKEN; + + const ol2d = new olMap({ + layers: [new olLayerTile({ source: new olSourceOSM() }), new olLayerImage({ source: customProjSource })], + target: mapRef.current, + view: new olView({ + center: [860434.6266531206, 6029479.0044273855], + zoom: 6, + }), + }); + + const ol3dInstance = new OLCesium({ + map: ol2d, + time: () => Cesium.JulianDate.now(), + }); + + const scene = ol3dInstance.getCesiumScene(); + Cesium.createWorldTerrainAsync().then(tp => { + scene.terrainProvider = tp; + }); + + ol3dInstance.setEnabled(true); + setOl3d(ol3dInstance); + + return () => { + ol3dInstance.setEnabled(false); + ol2d.setTarget(null); + }; + }, []); + + return
; +}; + +export default Google3DMap; diff --git a/packages/common/business_components/src/ui/map/MapToggleBtn.js b/packages/common/business_components/src/ui/map/MapToggleBtn.js new file mode 100644 index 000000000..fe8258ecb --- /dev/null +++ b/packages/common/business_components/src/ui/map/MapToggleBtn.js @@ -0,0 +1,32 @@ +import React, { useContext, useState } from 'react'; +import { Button } from "@common/components"; +import { GisContext } from './Gis'; +import GisApp from './lib/gis.core'; + +export default React.memo(function MapToggleBtn() { + const { gisCore } = useContext(GisContext); + const [activeMap, setActiveMap] = useState({ label: '일반', type: 'base' }); // 기본 선택값 + + const handleSelect = mapObj => { + setActiveMap(mapObj); + gisCore.changeBaseLayer(mapObj.type); + }; + + return ( +
+ +
    +
  • + +
  • +
  • + +
  • +
+
+ ); +}); diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.common.css b/packages/common/business_components/src/ui/map/lib/css/gis.common.css new file mode 100644 index 000000000..de98d8a9b --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/css/gis.common.css @@ -0,0 +1,1037 @@ +@charset "UTF-8"; + +.custom-toolbar { + position: absolute; + top: -20px; + right: 0px; +} +.mapbtn { + position: absolute; + top: 10px; + right: 20px; +} +.mapbtn h3 { + overflow: hidden; + font-size: 0; + line-height: 0; + text-indent: -9999px; +} +.mapbtn > button { + overflow: hidden; + width: 40px; + height: 38px; + background: url(../images/toolbar/ico_gis_sprite.png) #fff no-repeat; + border: 1px solid #9aa7b8; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; +} +.mapbtn .map1 { + background-image: url('../images/toolbar/img_map1.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + width: 60px; + height: 40px; +} +.mapbtn .map2 { + background-image: url('../images/toolbar/img_map2.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + width: 60px; + height: 40px; +} +.mapbtn > button > div { + display: none; + overflow: hidden; + text-indent: -9999px; +} +.mapbtn .viewer { + background-position: 0 -120px; +} +.mapbtn > a.danmyun { + background: url(../images/toolbar/ico_gis_sprite.png) #fff 0 -400px; +} +.mapbtn > a:hover { + margin: -1px; + border: 2px solid #1357ce; +} +.mapbtn .choice_map { + display: none; + position: absolute; + top: 0; + right: 20px; + width: 150px; +} +.mapbtn:hover ul.choice_map { + display: block; +} +.mapbtn ul.choice_map { + font-size: 0; +} +.mapbtn ul.choice_map li { + float: left; + margin-right: 5px; + background: #fff; + width: 60px; + text-align: center; + border: 2px solid transparent; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; + overflow: hidden; +} +.mapbtn ul.choice_map li:last-child { + margin-right: 0; +} +.mapbtn ul.choice_map > li:hover { + border: 2px solid #1357ce; +} +.mapbtn ul.choice_map > li.on { + border: 2px solid #1357ce; +} +.mapbtn ul.choice_map li span { + font-size: 12px; + color: #999; + text-align: center; +} +.mapbtn ul.choice_map li.on span { + font-size: 12px; + color: #333; + text-align: center; +} + +.tool-bar { + position: absolute; + top: 140px; + right: 20px; + z-index: 2; +} +.tool-bar h3 { + overflow: hidden; + font-size: 0; + line-height: 0; + text-indent: -9999px; +} +.tool-bar .status { + margin-bottom: 10px; + overflow: hidden; + width: 40px; + height: 40px; + background: #fff; + border: 1px solid #9aa7b8; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; + box-sizing: border-box; +} +.tool-bar .status div { + display: block; + width: 40px; + height: 40px; + background: url(../images/toolbar/ico_gis_sprite.png) no-repeat; + background-position: 0 -160px; + text-indent: -9999px; +} +.tool-bar .status div:hover { + background-position: -40px -160px; +} +.tool-bar .status div.on { + background: url(../images/toolbar/ico_gis_sprite.png) no-repeat; + background-position: 0 -200px; +} +.tool-bar .status div.on:hover { + background-position: -40px -200px; +} +.tool-bar ul { + display: inline-block; + overflow: hidden; + width: 40px; + background: #fff; + border: 1px solid #9aa7b8; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; + font-size: 0; + box-sizing: border-box; + padding: 0px; +} +.tool-bar ul li { + height: 40px; + border-bottom: 1px solid #eeeeee; + box-sizing: border-box; +} +.tool-bar ul li:last-child { + border-bottom: 0 none; +} +.tool-bar ul li div { + width: 40px; + height: 40px; + display: inline-block; +} +.tool-bar ul li div span { + display: none; + overflow: hidden; + text-indent: -9999px; +} + +.ol-scale-line { + background: rgba(0, 60, 136, 0.3); + border-radius: 4px; + padding: 2px; + position: absolute; + bottom: 10px; + right: 200px; +} + +/* 로드뷰 관련 */ +#roadview-container { + width: 100%; + height: 100%; + z-index: 500; + display: block; + position: unset; + left: 50%; + top: 0px; +} +#roadview-container .roadview-container-1p { + width: 50%; + height: 100%; + float: left; + position: relative; +} +#roadview-container .roadview-container-2p { + width: 50%; + height: 100%; + float: left; + position: relative; +} +#roadview-container .roadview { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + z-index: 0; + background: #999; +} + +/* Map Contents: Map Control Bar */ +.map_control { + position: absolute; + right: 20px; + top: 200px; + display: block; +} +.control_icons { + background: rgba(255, 255, 255, 0.9); + border-radius: 4px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + padding: 10px; + display: block; +} +.control_icons li a { + display: block; + width: 40px; + height: 40px; + font-size: 0; +} +.control_icons li a:hover { + border-radius: 100%; + box-shadow: 0 3px 5px rgba(0, 0, 0, 0.1); +} +.btn_control_zoomin { + background: url(../images/toolbar/btn_cont_zoomin.png) no-repeat center; +} +.btn_control_zoomin:hover { + background: url(../images/toolbar/btn_cont_zoomin_on.png) no-repeat center #67aee3; +} +.btn_control_zoomout { + background: url(../images/toolbar/btn_cont_zoomout.png) no-repeat center !important; +} +.btn_control_zoomout:hover { + background: url(../images/toolbar/btn_cont_zoomout_on.png) no-repeat center #67aee3; +} +.btn_control_prev { + background: url(../images/toolbar/btn_cont_prev.png) no-repeat center; +} +.btn_control_prev:hover { + background: url(../images/toolbar/btn_cont_prev_on.png) no-repeat center #67aee3; +} +.btn_control_next { + background: url(../images/toolbar/btn_cont_next.png) no-repeat center; +} +.btn_control_next:hover { + background: url(../images/toolbar/btn_cont_next_on.png) no-repeat center #67aee3; +} +.btn_control_ruler { + background: url(../images/toolbar/btn_cont_ruler.png) no-repeat center center; +} +.btn_control_ruler:hover { + background: url(../images/toolbar/btn_cont_ruler_on.png) no-repeat center #67aee3; +} +.btn_control_ruler.active { + background: url(../images/toolbar/btn_cont_ruler_on.png) no-repeat center #67aee3; +} +.btn_control_area { + background: url(../images/toolbar/btn_cont_area.png) no-repeat center; +} +.btn_control_area:hover { + background: url(../images/toolbar/btn_cont_area_on.png) no-repeat center #67aee3; +} +.btn_control_area.active { + background: url(../images/toolbar/btn_cont_area_on.png) no-repeat center #67aee3; +} +.btn_control_save { + background: url(../images/toolbar/btn_cont_save.png) no-repeat center; +} +.btn_control_save:hover { + background: url(../images/toolbar/btn_cont_save_on.png) no-repeat center #67aee3; +} +.btn_control_sketch { + background: url(../images/toolbar/btn_cont_sketch.png) no-repeat center; +} +.btn_control_sketch:hover { + background: url(../images/toolbar/btn_cont_sketch_on.png) no-repeat center #67aee3; +} +.btn_control_print { + background: url(../images/toolbar/btn_cont_print.png) no-repeat center; +} +.btn_control_print:hover { + background: url(../images/toolbar/btn_cont_print_on.png) no-repeat center #67aee3; +} + +/*하단 */ +.map-scale-area { + position: absolute; + bottom: 40px; + right: 200px; + display: inline-block; + z-index: 101; + list-style: none; +} +.map-scale-area li { + float: left; + margin-right: 15px; + font-size: 12px; + font-weight: 600; + color: #333; + text-shadow: -2px 0 #fff, 0 2px #fff, 2px 0 #fff, 0 -2px #fff; + -moz-text-shadow: -2px 0 #fff, 0 2px #fff, 2px 0 #fff, 0 -2px #fff; + -webkit-text-shadow: -2px 0 #fff, 0 2px #fff, 2px 0 #fff, 0 -2px #fff; +} +.map-scale-area li:last-child { + margin-right: 0; +} +.map-scale-area .scale-bar { + margin-right: 12px; + margin-top: 5px; + padding: 0 10px; + border: 2px solid #333; + border-top: 0 none; + height: 9px; + position: relative; + z-index: 999; +} +.map-scale-area .scale-bar .line-text { + position: relative; + top: -11px; +} + +/* sprite */ +.sprite_gis { + display: inline-block; /* overflow: hidden; */ + background: url(../images/toolbar/ico_gis_sprite.png) 0 0 no-repeat; + line-height: 999px; + vertical-align: bottom; +} + +.select_control { + position: absolute; + top: 80px; + left: 250px; +} +.select_control h3 { + overflow: hidden; + font-size: 0; + line-height: 0; + text-indent: -9999px; +} +.select_control ul { + display: inline-block; + overflow: hidden; + height: 40px; + font-size: 0; + background: #fff; + border: 1px solid #9aa7b8; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; + box-sizing: border-box; + padding: 0px; + margin: 0px; +} +.select_control ul li { + float: left; + width: 40px; + border-right: 1px solid #eeeeee; +} +.select_control ul li:last-child { + border-right: 0 none; +} +.select_control ul li div { + overflow: hidden; + width: 40px; + height: 40px; + background: url(../images/toolbar/ico_gis_sprite.png) no-repeat; +} +.select_control ul li div span { + display: none; + overflow: hidden; + text-indent: -9999px; +} +.select_control .sprite_gis.icon1 { + background-position: 0 0; +} +.select_control .sprite_gis.icon1:hover { + background-position: -40px 0; +} +.select_control .sprite_gis.icon2 { + background-position: 0 -40px; +} +.select_control .sprite_gis.icon2:hover { + background-position: -40px -40px; +} +.select_control .sprite_gis.icon3 { + background-position: 0 -80px; +} +.select_control .sprite_gis.icon3:hover { + background-position: -40px -80px; +} +.select_control .sprite_gis.icon4 { + background-position: 0 -360px; +} +.select_control .sprite_gis.icon4:hover { + background-position: -40px -360px; +} +.select_control .sprite_gis.icon4.active { + background-position: -40px -360px; +} + +/* 20230516 하수도 자산관리용 아이콘*/ +.sprite_gis.icon4 { + background-position: 0 -360px; +} +.sprite_gis.icon4:hover { + background-position: -40px -360px !important; + background-color: #67aee3; +} + +.select_control select { + float: right; + min-width: 100px; + height: 40px; + line-height: 40px; + margin-left: 10px; + padding-left: 10px; + font-size: 13px; + color: #666; + border: 1px solid #fff; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; +} + +.select_control-left0 { + left: 0px; +} + +/*** result_conts ***/ +.result_conts { + position: absolute; + top: 92px; + left: -320px; + height: 540px; + bottom: 0; + width: 320px; + border-right: 1px solid #bdbdbd; + box-shadow: 0px 0px 10px #999; + background: #fff; + box-sizing: border-box; + z-index: 500; + -webkit-transition: left 0.2s ease-out, opacity 0.3s ease-out; + -moz-transition: left 0.2s ease-out, opacity 0.3s ease-out; + transition: left 0.2s ease-out, opacity 0.3s ease-out; +} +.result_conts button.toggle { + display: block; + width: 30px; + height: 50px; + position: absolute; + top: 50%; + right: -30px; + margin-top: -28px; + background: url(../images/toolbar/toggle_off.png); + outline: none; + border: none; +} +.result_conts.on button.toggle { + background: url(../images/toolbar/toggle_on.png); +} +.result_conts.on { + left: 0; +} + +.result_conts .cheader { + padding: 16px 20px; + border-bottom: 1px solid #dddddd; + font-size: 14px; + line-height: 18px; + box-sizing: border-box; +} +.result_conts .cheader span.title { + color: #666; +} +.result_conts .cheader span.title em { + padding-right: 5px; + color: #152346; + font-weight: bold; +} +.result_conts .cbody { + position: absolute; + top: 51px; + bottom: 0; + left: 0; + right: 0; + padding: 15px 10px 0; + overflow: hidden; + overflow-y: auto; +} +.result_conts .cbody ul.list { + position: absolute; + top: 54px; + bottom: 41px; + left: 10px; + right: 10px; + overflow: hidden; + overflow-y: auto; + color: #999; + list-style: none; + padding: 0px; +} +.result_conts .cbody ul.list li { + border-bottom: 1px dotted #bbbbbb; +} +.result_conts .cbody .list li a { + color: rgb(98, 89, 202); + display: block; + width: 100%; + padding: 19px 0 15px 10px; +} +.result_conts .cbody .list li a .zip { + display: inline-block; + padding: 0 5px; + border: 1px solid #152346; + font-size: 12px; + color: #152346; + margin-right: 10px; +} +.result_conts .cbody .list li a .zip2 { + display: inline-block; + padding: 0 5px; + border: 1px solid #cc33cc; + font-size: 12px; + color: #cc33cc; +} +.result_conts .cbody .list li a .addr { + font-size: 0.875rem; + display: block; + padding-top: 5px; + color: #989898; +} +.t_count { + font-size: 0.875rem; +} + +/** 시설물정보 검색결과 하수관용도별 색상구분 : 하수관로 **/ +.result_conts .cbody .list li a .addr .SBA000_facility_init { + color: #999999; +} +.result_conts .cbody .list li a .addr .SBA001_facility_init { + color: #cc9933; +} +.result_conts .cbody .list li a .addr .SBA002_facility_init { + color: #ff9900; +} +.result_conts .cbody .list li a .addr .SBA003_facility_init { + color: #0000ff; +} +.result_conts .cbody .list li a .addr .SBA004_facility_init { + color: #cc0000; +} +.result_conts .cbody .list li a .addr .SBA999_facility_init { + color: #222222; +} + +/** 시설물정보 검색결과 하수관용도별 색상구분 : 하수맨홀 **/ +.result_conts .cbody .list li a .addr .SMU000_facility_init { + color: #999999; +} +.result_conts .cbody .list li a .addr .SMU001_facility_init { + color: #0000ff; +} +.result_conts .cbody .list li a .addr .SMU002_facility_init { + color: #cc0000; +} +.result_conts .cbody .list li a .addr .SMU003_facility_init { + color: #ff9900; +} +.result_conts .cbody .list li a .addr .SMU004_facility_init { + color: #cc9933; +} +.result_conts .cbody .list li a .addr .SMU999_facility_init { + color: #222222; +} + +/** 시설물정보 검색결과 하수관용도별 색상구분 : 물받이 **/ +.result_conts .cbody .list li a .addr .SBD000_facility_init { + color: #999999; +} +.result_conts .cbody .list li a .addr .SBD001_facility_init { + color: #0000ff; +} +.result_conts .cbody .list li a .addr .SBD002_facility_init { + color: #cc0000; +} +.result_conts .cbody .list li a .addr .SBD003_facility_init { + color: #ff9900; +} +.result_conts .cbody .list li a .addr .SBD999_facility_init { + color: #222222; +} + +.result_conts .cbody .list li.on, +.result_conts .cbody .list li:hover { + background-color: #ebebeb; +} +.result_conts .cbody .list li.on a .zip, +.result_conts .cbody .list li:hover a .zip { + color: #fff; + background-color: #152346; +} +.result_conts .cbody .list li.on a .addr, +.result_conts .cbody .list li:hover a .addr { + color: #000; +} + +.result_conts .cbody .paginate { + position: absolute; + bottom: 0; + left: 20px; + right: 0; + padding: 10px 0; + font-size: 11px; + line-height: normal; + text-align: center; +} + +/*** 페이징 **/ +.paginate { + padding: 10px 0; + font-size: 11px; + line-height: normal; + text-align: center; +} +.result_conts .cbody .paginate .pagination .page-link { + font-size: 11px !important; +} + +/*** 검색결과 더보기 **/ +.result_facility { + position: absolute; + left: 320px; + bottom: 0; + width: 1280px; + height: 200px; + border-radius: 10px; + border-right: 1px solid #bdbdbd; + box-shadow: 0px 0px 10px #999; + background: #fff; + box-sizing: border-box; + z-index: 500; + -webkit-transition: left 0.2s ease-out, opacity 0.3s ease-out; + -moz-transition: left 0.2s ease-out, opacity 0.3s ease-out; + transition: left 0.2s ease-out, opacity 0.3s ease-out; +} + +.result_facility_popup { + width: 1130px; +} + +.result_facility .cheader { + padding: 16px 20px; + border-bottom: 1px solid #dddddd; + font-size: 35px; + line-height: 18px; + box-sizing: border-box; + color: #666; + text-align: center; +} + +/*** 상수도 시설현황 **/ +.fcltyLayerSmmyDiv { + position: absolute; + top: 10px; + left: 500px; + bottom: 0; + background: #fff; + width: 130px; + overflow-x: hidden; + height: 40px; + overflow-y: hidden; + border-radius: 5px; +} +.fcltyLayerSmmyHeaderArea { + background: #ffffff; + color: black; +} +.fcltyLayerSmmyHeaderArea span { + color: #000000; + font-size: 16px; + padding-left: 10px; + line-height: 40px; +} + +.assetsSmmyDiv { + position: absolute; + top: 55px; + left: 320px; + bottom: 0; + background: #fff; + width: 310px; + overflow-x: hidden; + height: 690px; + overflow-y: auto; + border-radius: 5px; +} +.assetsNotMappingDiv { + position: absolute; + top: 10px; + right: 20px; + bottom: 0; + background: #fff; + width: 180px; + overflow-x: hidden; + height: 70px; + overflow-y: hidden; + border-radius: 5px; +} +.assetsNotAcceptDiv { + position: absolute; + top: 80px; + right: 20px; + bottom: 0; + background: #fff; + width: 180px; + overflow-x: hidden; + height: 70px; + overflow-y: hidden; + border-radius: 5px; +} + +.puDiv { + position: relative; + width: 390px; + height: 180px; + background: #fff; + margin: 4px auto 10px; + padding: 0; + border-bottom: 0px; +} +.puDiv .puHeader { + display: block; + position: relative; + margin-left: 10px; +} +.puDiv .puContent { + display: block; + position: relative; + width: 395px; + padding: 2px; + text-align: center; + margin-left: 10px; +} +.puGrayTb { + position: relative; + width: 98.3%; + cellpadding: 0; + cellspcing: 0; + margin-top: 5px; + table-layout: fixed; + font-family: Verdana; +} +.puGrayTb th { + text-align: center; + height: 29px; + background: #b9d5ff; + color: #454545; + border: 1px solid #9d9d9d; + font-size: 12px; + font-weight: bold; +} +.puGrayTb td { + text-align: center; + height: 15px; + background: #fff; + color: #454545; + padding-left: 5px; + font-size: 12px; + border: 1px solid #cbcbcb; + font-weight: bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.puHeaderArea { + position: relative; + height: 40px; +} +.assetsSmmyHeaderArea { + background: #00c73c; + margin-bottom: 7px; +} +.assetsSmmyHeaderArea span { + color: #fff; + font-size: 16px; + padding-left: 10px; + line-height: 40px; +} +.puHeaderArea .close { + position: relative; + top: 5px; + float: right; + color: #fff; + background: url('../images/toolbar/icon-viewclose.png') no-repeat; + text-indent: -9999px; + width: 30px; + height: 30px; +} +.header h3:after { + content: ''; + display: block; + background: #00a0ea; + width: 20px; + height: 3px; + font-size: 0; + margin-top: 5px; +} + +.switch { + position: relative; + display: inline-block; + width: 40px; + height: 22px; + left: 10px; + bottom: 2px; +} +.switch input { + opacity: 0; + width: 0; + height: 0; +} +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: 0.4s; + transition: 0.4s; + border-radius: 34px; +} +.slider:before { + position: absolute; + content: ''; + height: 15px; + width: 16px; + left: 4px; + bottom: 3.55px; + background-color: white; + -webkit-transition: 0.4s; + transition: 0.4s; + border-radius: 50%; +} +input:checked + .slider { + background-color: #7e7d7d; +} +input:focus + .slider { + box-shadow: 0 0 1px #7e7d7d; +} +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(16px); +} +.value { + position: absolute; + top: -11px; + left: -44px; +} + +table.board-table th { + text-align: center; + padding: 4px 0 !important; + border: 1px solid #bdbdbd52; + color: #000000; + font-size: 11px; + width: 50px; + background: #ffffff; +} +table.board-table td { + text-align: center; + padding: 4px 0 !important; + border: 1px solid #f1eded; + color: #000000; + font-size: 11px; + width: 50px; + background: #ffffff; +} +table.board-table thead .red { + background: #ff0000; + color: #ffffff; + font-size: 12px; + text-align: center; + padding: 5px; + font-family: Verdana; +} +table.board-table thead .blue { + background: #2a63dd; + color: #ffffff; + font-size: 12px; + text-align: center; + padding: 5px; + font-family: Verdana; +} +table.board-table thead .green { + background: green; + color: #ffffff; + font-size: 12px; + text-align: center; + padding: 5px; + font-family: Verdana; +} +table.board-table thead .gray { + background: #c0c0c0; + color: #ffffff; + font-size: 12px; + text-align: center; + padding: 5px; + font-family: Verdana; +} +table.board-table tbody th td { + font-size: 13px; + font-weight: bold; + font-family: Verdana; +} + +.ol-custom-popup { + position: absolute; + background-color: #fff; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); + padding: 15px; + border-radius: 10px; + border: 1px solid #ccc; + bottom: 12px; + left: -50px; + min-width: 280px; + font-size: 15; +} +.ol-custom-popup:after, +.ol-popup:before { + top: 100%; + border: solid transparent; + content: ' '; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} +.ol-custom-popup:after { + border-top-color: #fff; + border-width: 10px; + left: 48px; + margin-left: -10px; +} +.ol-custom-popup:before { + border-top-color: #ccc; + border-width: 11px; + left: 48px; + margin-left: -11px; +} +.ol-custom-popup-closer { + text-decoration: none; + position: absolute; + top: 2px; + right: 8px; +} +.ol-custom-popup-closer:after { + content: '✖'; +} +.ol-overviewmap { + width: 150px; + position: absolute; + z-index: 1; + right: 0px; + bottom: 0px; +} +.ol-overviewmap .ol-overviewmap-map { + border: 1px solid #7b98bc; + height: 100px; + margin: 10px; +} +.ol-overviewmap-box { + border: 2px dotted rgba(0, 60, 136, 0.7); +} +.ol-overviewmap button { + display: none; +} +.ol-overviewmap-map canvas { + height: 100%; +} + +.gis-map canvas { + transform: matrix(1, 0, 0, 1, 0, 0) !important; + width: inherit; + height: inherit; +} + +.gis-content .evl-legends{ + bottom: -20px !important; + left: 1350px; + z-index: 101; +} + + +/* cesium-viewer-toolbar 숨기기 */ +.cesium-viewer-toolbar { + display: none; +} + +/* cesium-viewer-animationContainer 숨기기 */ +.cesium-viewer-animationContainer { + display: none; +} +.cesium-viewer-bottom { + display: none; +} +.cesium-viewer-timelineContainer { + display: none; +} +.cesium-credit-logoContainer{ + display: none !important; +} +.cesium-credit-wrapper{ + display: none !important; +} +.cesium-credit-expand-link{ + display: none !important; +} + + diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.edit.css b/packages/common/business_components/src/ui/map/lib/css/gis.edit.css new file mode 100644 index 000000000..c2531e4b0 --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/css/gis.edit.css @@ -0,0 +1,192 @@ +@charset 'utf-8'; + +.edit_control { + position: absolute; + top: 20px; + left: 610px; +} +.edit_control h3 { + overflow: hidden; + font-size: 0; + line-height: 0; + text-indent: -9999px; +} +.edit_control ul { + float: left; + overflow: hidden; + height: 40px; + margin-left: 10px; + background: #fff; + font-size: 0; + border: 1px solid #9aa7b8; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; + box-sizing: border-box; + padding: 0; + cursor: pointer; +} +.edit_control ul li { + display: inline-block; + width: 40px; + height: 40px; + border-right: 1px solid #eeeeee; +} +.edit_control ul li:last-child { + border-right: 0 none; +} +/*background 삭제*/ +/* .edit_control ul li a {overflow:hidden;width: 40px;height: 40px;background: url(../../images/om/ico_gis_sprite.png) no-repeat;} */ +.edit_control ul li a { + overflow: hidden; + width: 40px; + height: 40px; + display: inline-block; + padding: 0; +} +.edit_control ul li a span { + display: none; + overflow: hidden; + text-indent: -9999px; +} +.edit_control select { + float: left; + min-width: 150px; + height: 40px; + line-height: 40px; + margin-left: 10px; + padding-left: 10px; + font-size: 13px; + color: #666; + border: 1px solid #fff; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; +} + +.edit_control input { + float: left; + min-width: 150px; + height: 40px; + line-height: 40px; + margin-left: 10px; + padding-left: 10px; + font-size: 13px; + color: #666; + border: 1px solid #fff; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; +} + +.edit_control button { + float: left; + min-width: 80px; + height: 40px; + margin-left: 5px; + font-size: 13px; + color: #fff; + text-align: center; + border-radius: 3px; + cursor: pointer; +} +.edit_control button:hover { + background: #1357ce; +} + +.edit_toolbar { + position: absolute; + left: 280px; + top: 52px; +} +.btn_edit_slt { + background: url(../images/toolbar/btn_edit_slt.png) no-repeat center; + height: 40px; +} +.btn_edit_slt:hover { + background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center; + height: 40px; +} +.btn_edit_slt.active { + background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center; + height: 40px; +} +.btn_edit_add { + background: url(../images/toolbar/btn_edit_add.png) no-repeat center; + height: 40px; +} +.btn_edit_add:hover { + background: url(../images/toolbar/btn_edit_add_on.png) no-repeat center; + height: 40px; +} +.btn_edit_add.active { + background: url(../images/toolbar/btn_edit_add_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_del { + background: url(../images/toolbar/btn_edit_del.png) no-repeat center; + height: 40px; +} + +.btn_edit_del:hover { + background: url(../images/toolbar/btn_edit_del_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_prev { + background: url(../images/toolbar/btn_edit_prev.png) no-repeat center; + height: 40px; +} + +.btn_edit_prev:hover { + background: url(../images/toolbar/btn_edit_prev_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_next { + background: url(../images/toolbar/btn_edit_next.png) no-repeat center; + height: 40px; +} + +.btn_edit_next:hover { + background: url(../images/toolbar/btn_edit_next_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_snap { + background: url(../images/toolbar/btn_edit_snap.png) no-repeat center; + height: 40px; +} + +.btn_edit_snap:hover { + background: url(../images/toolbar/btn_edit_snap_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_snap.active { + background: url(../images/toolbar/btn_edit_snap_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_save { + background: url(../images/toolbar/btn_edit_save.png) no-repeat center; + height: 40px; +} + +.btn_edit_save:hover { + background: url(../images/toolbar/btn_edit_save_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_transform { + background: url(../images/toolbar/btn_edit_slt.png) no-repeat center; + height: 40px; +} + +.btn_edit_transform:hover { + background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center; + height: 40px; +} + +.btn_edit_transform.active { + background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center; + height: 40px; +} diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.roadview.css b/packages/common/business_components/src/ui/map/lib/css/gis.roadview.css new file mode 100644 index 000000000..917d1b778 --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/css/gis.roadview.css @@ -0,0 +1,7 @@ +@charset 'utf-8'; +/* 로드뷰 관련 */ +.roadview-container {width: 100%; height: 100%; z-index:500; display:block;position: absolute; left: 0px;top: 0px;} +.roadview-container .roadview-container-1p {width: 310px; height: 190px; bottom: 15px; left: 15px; top: auto; border-radius: 2px; overflow: hidden; z-index: 1000; position: absolute;} +.roadview-container .roadview-container-2p {width: 100%; height:100%; float:left; position:relative;} +.roadview-container .roadview {position:absolute; top:0; right:0; left:0; bottom:0; z-index:0; background:#999;} + diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.sketch.css b/packages/common/business_components/src/ui/map/lib/css/gis.sketch.css new file mode 100644 index 000000000..e69c02a7a --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/css/gis.sketch.css @@ -0,0 +1,206 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ""; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + background-color: #e6e6fc; + display: flex; + flex-direction: column; + align-items: center; + padding: 50px 0px; +} + +.canvas { + width: 800px; + height: 500px; + background-color: white; + border-radius: 15px; + box-shadow: 6px 6px 5px grey; +} + +.controls .controls__colors { + display: flex; +} +.controls__colors .controls__color { + width: 50px; + height: 50px; + border-radius: 25px; + margin-right: 5px; + cursor: pointer; + box-shadow: 3px 3px 5px grey; +} + +.jscolor { + color: transparent; + font-size: 0; + line-height: 0; + + width: 50px; + height: 50px; + border-radius: 25px; + border-width: 0px; + margin-right: 5px; + cursor: pointer; + box-shadow: 3px 3px 5px grey; +} + +.controls { + display: flex; + flex-direction: column; + align-items: center; +} + +.controls .controls__btns { + margin-top: 20px; + margin-bottom: 30px; +} + +.controls__btns button:active { + transform: scale(0.98); +} + +.controls__btns button { + all: unset; + cursor: pointer; + background-color: white; + padding: 5px 0px; + width: 80px; + text-align: center; + border-radius: 10px; + box-shadow: 3px 3px 3px grey; + border: 2px solid rgba(0, 0, 0, 0.2); + color: rgba(0, 0, 0, 0.8); + text-transform: uppercase; + font-weight: 600; + font-size: 12px; +} + +.controls .controls__range { + margin-top: 20px; +} diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.tooltip.css b/packages/common/business_components/src/ui/map/lib/css/gis.tooltip.css new file mode 100644 index 000000000..043d842e2 --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/css/gis.tooltip.css @@ -0,0 +1,35 @@ +@charset 'utf-8'; + +.ol-tooltip { + position: relative; + background: rgba(0, 0, 0, 0.5); + border-radius: 4px; + color: white; + padding: 4px 8px; + opacity: 0.7; + white-space: nowrap; + font-size: 12px; +} +.ol-tooltip-measure { + opacity: 1; + font-weight: bold; +} +.ol-tooltip-static { + background-color: #ffcc33; + color: black; + border: 1px solid white; +} +.ol-tooltip-measure:before, +.ol-tooltip-static:before { + border-top: 6px solid rgba(0, 0, 0, 0.5); + border-right: 6px solid transparent; + border-left: 6px solid transparent; + content: ""; + position: absolute; + bottom: -6px; + margin-left: -7px; + left: 50%; +} +.ol-tooltip-static:before { + border-top-color: #ffcc33; +} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/map/lib/gis.core.js b/packages/common/business_components/src/ui/map/lib/gis.core.js new file mode 100644 index 000000000..faebb90d3 --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/gis.core.js @@ -0,0 +1,923 @@ +import MousePosition from 'ol/control/MousePosition'; +import ScaleLine from 'ol/control/ScaleLine'; +import Feature from 'ol/Feature'; +import WKT from 'ol/format/WKT'; +import LinearRing from 'ol/geom/LinearRing'; +import Draw from 'ol/interaction/Draw'; +import TileLayer from 'ol/layer/Tile'; +import * as olProj from 'ol/proj'; +import VectorSource from 'ol/source/Vector'; +import XYZ from 'ol/source/XYZ'; +import GeoSJON from 'ol/format/GeoJSON'; +import { Fill, Stroke, Style } from 'ol/style'; +import CircleStyle from 'ol/style/Circle'; +import VectorLayer from 'ol/layer/Vector'; +import { Point } from 'ol/geom'; +import { boundingExtent } from 'ol/extent'; +import olSourceImageWMS from 'ol/source/ImageWMS'; +import { GeoJSON } from 'ol/format'; +import GisOlEditLayer from './gis.edit'; +import GisOlLayer from './gis.layer'; + +const GisApp = {}; +/** + * GisApp 전역변수 생성 + */ +//레이어 정보 +GisApp.layerInfos = []; +//기본정보 +//GisApp.GEOSERVER_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/ows'; +//GisApp.GEOSERVER_WMS_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/wms'; +GisApp.GEOSERVER_URL = '/sn/geoserver/hasudo/ows'; +GisApp.GEOSERVER_WMS_URL = '/sn/geoserver/hasudo/wms'; + +//맵 정보 +GisApp.EMAP_URL = '/emap/'; +GisApp.EMAP_SATELLITE_URL = '/emap/satellite/'; +GisApp.VWORLD_URL = 'https://xdworld.vworld.kr/2d/Base/service/{z}/{x}/{y}.png'; +GisApp.VWORLD_SATELLITE_URL = 'https://xdworld.vworld.kr/2d/Satellite/service/{z}/{x}/{y}.jpeg'; +GisApp.GOOGLE_API_KEY = 'AIzaSyCf1sgeELFalfPuqlykRKBMj6X9u_DQH6M'; +GisApp.GOOGLE_ROADMAP_URL = `https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`; +GisApp.GOOGLE_SATELLITE_URL = `https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`; +GisApp.GOOGLE_HYBRID_URL = `https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`; +GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${GisApp.GOOGLE_API_KEY}`; +GisApp.OPENSTREET_URL = '//tile.openstreetmap.org/{z}/{x}/{y}.png'; +GisApp.BASE_MAP_TYPES = []; + +GisApp.FROM_PROJ = 'EPSG:4326'; +GisApp.TO_PROJ = 'EPSG:3857'; +GisApp.TO_DATA_PROJ = 'EPSG:4326'; +GisApp.CONTEXT_LAYER_PATH = '/sn/images/map/layericon/'; +GisApp.layerCode = {}; +GisApp.hlLayerId = 'LightFeatureList'; +GisApp.hLayerId = 'highLightFeature'; +GisApp.wtlkgAnal = 'wtlkgAnal'; + +GisApp.Module = { + core: null, + select: null, + measure: null, + layer: null, + style: null, + edit: null, +}; + +/** + * GisApp.layerInfos Set + */ +GisApp.setLayerInfos = function (layerInfos) { + GisApp.layerInfos = layerInfos; +}; + +/** + * GisApp core + * @requires gis.layer.js + */ +GisApp.core = function (options) { + GisApp.core.prototype.initialize(options); +}; + +GisApp.core.prototype = { + defaults: { + target: '', + centerX: 37.2806094, + centerY: 127.0032502, + zoom: 11, + minZoom: 5, + maxZoom: 22, + }, + options: { + /*1: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.3227711, centerY: 129.1836202, dataProj: 'EPSG:5186' }, + 4: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.0140499, centerY: 126.7110757, dataProj: 'EPSG:5186' }, + 14: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 34.9390499, centerY: 127.6960757, dataProj: 'EPSG:5186' },*/ + }, + epsg_5179: null, + map: null, // ol.Map + view: {}, // ol.view + interactions: {}, // interaction 모음 객체 + selectedFeature: null, // 현재 선택된 feature + baseLayers: {}, + baseMiniMapLayers: {}, + layers: {}, + initialize: function (options) { + let option = this.options['1']; + if (option) { + Object.assign(this.defaults, option); + GisApp.TO_DATA_PROJ = option.dataProj; + } + Object.assign(this.defaults, options); + GisApp.Module.core = this; + GisApp.Module.layer = new GisOlLayer(this.defaults); + GisApp.Module.edit = new GisOlEditLayer(this.defaults); + + this.baseMiniMapLayers['base'] = this.getBaseMap('minimap-base'); + this.baseLayers['base'] = this.getBaseMap('base'); + this.baseLayers['satellite'] = this.getBaseMap('satellite'); + this.baseLayers['3d'] = this.getBaseMap('3d'); + }, + layerType: { + point: 'POINT', + line: 'LINE', + poligon: 'POLIGON', + }, + chgMngFcltOptions: function (fcltCd) { + let option = this.options[fcltCd]; + Object.assign(this.defaults, option); + this.setCenter(Number(this.defaults.centerX), Number(this.defaults.centerY)); + this.setZoom(15); + }, + getBaseMap: function (_id) { + let url = GisApp.VWORLD_URL; + if (_id === 'satellite') { + url = GisApp.VWORLD_SATELLITE_URL; + } + let baseMap = new TileLayer({ + id: _id, + visible: true, + type: 'base', + source: new XYZ({ + url: url, + crossOrigin: 'anonymous', + }), + zIndex: 0, + }); + return baseMap; + }, + /** + * [사용]테이블명으로 레이어 아이디 조회 + */ + getLayerCodeByTbNm: function (_tableNm) { + let returnLayerCode = null; + for (let i in GisApp.layerCode) { + let layerCode = GisApp.layerCode[i]; + if (layerCode.tableName === _tableNm) { + returnLayerCode = layerCode; + } + } + return returnLayerCode; + }, + /** + * [사용]베이스 레이어 변경 + * Parameters: + * _title - {String} 베이스 레이어 이름 + * + * Returns: + */ + changeBaseLayer: function (_id) { + this.map.getLayers().forEach(function (layer) { + let type = layer.get('type'); + let id = layer.get('id'); + if (type == 'base') { + if (id == _id) { + layer.setVisible(true); + } else { + layer.setVisible(false); + } + } + }); + }, + /** + * [사용]지도 중심좌표 설정 + */ + setCenter: function (x, y, transYn) { + if (!x || !y) return; + let view = this.map.getView(); + + if (!transYn) { + // 값이 없으면 + let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ); + const style = new Style({ + image: new CircleStyle({ + radius: 5, + fill: new Fill({ + color: 'rgba(255,0,0,1)', + }), + stroke: new Stroke({ + color: 'rgba(255,0,0,1)', + width: 2, + }), + }), + }); + + // 빨간색 점 추가 (스타일 적용) + let redPoint = new Feature({ + geometry: new Point(center), + }); + + let redPointLayer = new VectorLayer({ + source: new VectorSource({ + features: [redPoint], + }), + style: style, + }); + redPointLayer.set('id', 'redPointLayer'); + // 이전에 만들었던 빨간색 점 레이어 삭제 + this.deleteLayer('redPointLayer'); + + // 새로운 빨간색 점 레이어를 맵에 추가 + this.map.addLayer(redPointLayer); + view.setCenter(center); + } else { + view.setCenter([Number(x), Number(y)]); + } + }, + + /** + * [사용]주소검색 결과 지도이동 + */ + setAddressResult: function (x, y, geom, addressName) { + if (!x || !y) return; + let view = this.map.getView(); + let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ); + + const style = [ + new Style({ + image: new CircleStyle({ + radius: 5, + fill: new Fill({ + color: 'rgba(255,0,0,1)', + }), + stroke: new Stroke({ + color: 'rgba(255,0,0,1)', + width: 2, + }), + }), + }), + new Style({ + stroke: new Stroke({ + color: 'blue', + width: 3, + }), + fill: new Fill({ + color: 'rgba(0, 0, 255, 0.1)', + }), + }), + ]; + + // 빨간색 점 추가 (스타일 적용) + let redPoint = new Feature({ + geometry: new Point(center), + }); + let redArea = new WKT().readFeature(geom, { + dataProjection: GisApp.FROM_PROJ, + featureProjection: GisApp.TO_PROJ, + }); + + let redPointLayer = new VectorLayer({ + source: new VectorSource({ + features: [redPoint, redArea], + }), + style: style, + }); + + redPointLayer.set('id', 'redPointLayer'); + // 이전에 만들었던 빨간색 점 레이어 삭제 + this.deleteLayer('redPointLayer'); + + // 새로운 빨간색 점 레이어를 맵에 추가 + this.map.addLayer(redPointLayer); + + view.animate({ center: center, zoom: 19 }); + return redPointLayer; + }, + /* + * [사용]zoom 설정 + */ + setZoom: function (zoom) { + if (!zoom) return; + this.map.getView().setZoom(zoom); + }, + /* + * [사용]zoom 정보 반환 + */ + getZoom: function () { + return this.map.getView().getZoom(); + }, + /** + * [사용]레이어 맵에 추가(외부에서 생성된 layer를 파라미터로 받아 맵에 추가) + */ + setWfsLayerOnMap: function (layer) { + this.deleteLayer(layer.get('id')); + this.map.addLayer(layer); + return layer; + }, + /** + * [사용]레이어 생성(지오서버에서 WFS 방식으로 레이어정보 가져와 레이어 생성) + */ + getWfsLayer: function (layerCodeObj) { + return GisApp.Module.layer.getWfsLayer(layerCodeObj); + }, + /** + * [사용]레이어 맵에 추가(getWfsLayer 사용하여 레이어 생성) + */ + addWfsLayer: function (layerCodeObj) { + let layer = this.getWfsLayer(layerCodeObj); + this.deleteLayer(layer.get('id')); + this.map.addLayer(layer); + return layer; + }, + /** + * [사용]레이어 맵에 추가(getWmsLayer 사용하여 레이어 생성) + */ + addWmsLayer: function (layerCodeObj) { + let layer = GisApp.Module.layer.getWmsLayer(layerCodeObj); + this.deleteLayer(layer.get('id')); + this.map.addLayer(layer); + return layer; + }, + /** + * [사용]레이어 삭제 + */ + deleteLayer: function (layerId) { + const map = this.map; + if (!layerId) return; + map.getLayers().forEach(function (layer) { + let deleteLayer = null; + if (layer != null && layer.get('id') != null) { + let vlayerId = layer.get('id'); + if (vlayerId) { + if (vlayerId === layerId) { + deleteLayer = layer; + } else if (`${vlayerId}`.includes(`${layerId}`)) { + deleteLayer = layer; + } + if (deleteLayer) { + map.removeLayer(deleteLayer); + } + } + } + }); + }, + /** + * [사용]레이어 전체 삭제 + */ + deleteAllLayer: function () { + const map = this.map; + map.getLayers().forEach(function (layer) { + if (layer?.get('type') !== 'base') { + map.removeLayer(layer); + } + }); + //맵에 보이지 않는 레이어 삭제 + GisApp.Module.layer?.layers.forEach(function (layer) { + map.removeLayer(layer); + }); + }, + /** + * [사용]레이어이름으로 레이어 조회 반환 + */ + getLayerById: function (layerId) { + if (!layerId) return; + let layer = null; + this.map.getAllLayers().forEach(function (el) { + if (el != null && el?.get('id') != null) { + let vlayerId = el?.get('id'); + if (Number(vlayerId) === Number(layerId)) { + layer = el; + } + } + }); + if (layer != null) { + GisApp.Module.layer.layers.forEach(function (el) { + if (el != null && el?.get('id') != null) { + let vlayerId = el?.get('id'); + if (Number(vlayerId) === Number(layerId)) { + layer = el; + } + } + }); + } + return layer; + }, + /** + * [사용]오버레이 전체 삭제 + */ + deleteAllOverlay: function () { + let map = this.map; + let overlays = map.getOverlays(); + if (overlays != null) { + let overlayArray = overlays.getArray(); + let overlaySize = overlayArray.length; + if (overlaySize > 0) { + for (let i = overlaySize - 1; i >= 0; i--) { + let overlay = overlayArray[i]; + map.removeOverlay(overlay); + } + } + } + Object.values(GisApp.Module.style.overlays).forEach(overlay => { + if (overlay) { + map.removeOverlay(overlay); + } + }); + GisApp.Module.style.overlays = {}; + }, + /** + * [사용]오버레이 아이디로 삭제 + */ + deleteOverlay: function (_id) { + let map = this.map; + let overlays = map.getOverlays(); + if (overlays != null) { + let overlayArray = overlays.getArray(); + let overlaySize = overlayArray.length; + if (overlaySize > 0) { + for (let i = overlaySize - 1; i >= 0; i--) { + let overlay = overlayArray[i]; + let id = overlay.getId(); + if (id === _id) { + map.removeOverlay(overlay); + delete GisApp.Module.style.overlays[_id]; + } + } + } + } + }, + /** + * [사용]맵 이벤트 삭제 + */ + removeMapEventFnNm: function (handlerNm, fnNm) { + let map = this.map; + for (let i in map.listeners_[handlerNm]) { + let listener = map.listeners_[handlerNm][i]; + if (listener.name == fnNm) { + map.listeners_[handlerNm].splice(i, 1); + } + } + }, + /** + * [사용]위경도 -> 시분초 변환 + */ + coordTrans: function (coord) { + let str = String(coord); /* String 변환 */ + let split = str.split('.'); /* 소수점 split */ + let hour = split[0]; /* 시간 */ + let minute = parseFloat((coord - parseInt(split[0]).toFixed(6)) * 60).toFixed(6); + let min = String(minute).split('.')[0]; + let second = parseFloat((minute - parseInt(min)) * 60).toFixed(2); + return hour + '도 ' + min + '분 ' + second + '초'; + }, + /** + * [사용]시분초 -> 위경도 변환 + */ + coordTransrate: function (degree, minute, second) { + let min = (Number(minute) / 60).toFixed(2); + let sec = (Number(second) / 3600).toFixed(4); + return degree + Number(min) + Number(sec); + }, + /** + * [사용]마우스 좌표 위치 + */ + mousePositionControl: function () { + let element = document.getElementById('label_lonlat'); + let me = this; + let mousePosition = new MousePosition({ + coordinateFormat: function (coordinate) { + let text = ''; + if (element) { + let lng = coordinate[0]; + let lat = coordinate[1]; + text += '경도 : ' + lng.toFixed(7) + ' ( ' + me.coordTrans(lng) + ' ) '; + text += '위도 : ' + lat.toFixed(7) + '( ' + me.coordTrans(lat) + ' )'; + } + return text; + }, + projection: GisApp.FROM_PROJ, + className: 'custom-mouse-position', + target: document.getElementById('label_lonlat'), + }); + this.map.addControl(mousePosition); + }, + /** + * [사용]스케일 표시 + */ + scaleControl: function () { + let scale = new ScaleLine({ + minWidth: 100, + units: 'metric', + }); + this.map.addControl(scale); + }, + /** + * [사용]레이어 맵에 셋팅 + * + * Parameters: + * layerList - {Array} [layerNm : GisApp.layerCode 키] + * visible - {Boolean} 레이어 visible 여부 + * + * Returns: + */ + setLayerOnMap: function (layerIds, visible, addQurryFlter) { + for (let i in layerIds) { + let id = layerIds[i]; + let layer = this.getLayerById(id); + if (layer) { + layer.setVisible(visible); + } else if (visible) { + const layerCode = GisApp.layerCode[id]; + const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || ''); + layerCode['flterSql'] = queryFlter; + //layer = this.addWmsLayer(layerCode); + layer = this.addWfsLayer(layerCode); + } + } + if (GisApp.Module.select) { + this.setSelectInteraction(); + } + if (GisApp.Module.edit.snap) { + GisApp.Module.edit.setSnap(); + } + }, + /** + * [사용]shp 파일 다운로드 + */ + getShapeLayer: function (option) { + GisApp.Module.layer.getShapeLayer(option); + }, + /** + * [사용]레이어 셀렉트 가능하게 셋팅 + */ + setSelectInteraction: function () { + let layers = this.map.getLayers(); + if (layers) { + for (let i in layers.array_) { + let layer = layers.array_[i]; + if (layer.get('chcYn') === 'Y') { + GisApp.Module.select.addLayer(layer); + } + } + } + }, + /** + * [사용]layerInfos(array) To layerCode(Object) + */ + convertLayerInfosToLayerCode: function (array, key, parentName) { + let initialValue = {}; + + for (let i = 0; i < array.length; i++) { + let item = {}; + Object.assign(item, array[i]); + if (parentName) { + item.parentName = parentName; + } + initialValue[item[key]] = item; + + if (item.children && item.children.length > 0) { + let child = this.convertLayerInfosToLayerCode(item.children, key, item.name); + Object.assign(initialValue, child); + } + if (initialValue[item[key]].children) { + delete initialValue[item[key]].children; + } + } + return initialValue; + }, + /** + * [사용]피처 좌표값 WKT포맷으로 변경 + */ + convertFeatureGeomToWKT: function (feature) { + let geometry = feature.getGeometry(); + const wktGeom = this.convertGeomToWkt(geometry); + return wktGeom; + }, + /** + * [사용]WKT포맷 Geometry로 변경 + */ + convertWktToGeom: function (wtkGeom) { + let format = new WKT(); + let geom = format.readGeometry(wtkGeom); + return geom; + }, + /** + * [사용]좌표와 properties의 키값으로 피처 검색 + */ + getFeatureGeomProp: function (geometry, ftrIds, ftrCde) { + let returnFeature; + this.map.getLayers().forEach(function (layer) { + let type = layer.get('type'); + if (type != 'base') { + let source = layer.getSource(); + + let type = geometry.getType(); + let coordinates = geometry.getCoordinates(); + if (type.toUpperCase() === 'POINT') { + coordinates = [coordinates]; + } else { + coordinates = coordinates[0]; + } + let featureFtrIds; + let featureFtrCde; + for (let i in coordinates) { + let coordinate = coordinates[i]; + let feature = source.getClosestFeatureToCoordinate(coordinate); + if (feature) { + let properties = feature.getProperties(); + featureFtrIds = properties['FTR_IDS']; + featureFtrCde = properties['FTR_CDE']; + if (ftrIds == featureFtrIds && ftrCde == featureFtrCde) { + returnFeature = feature; + } + } + } + } + }); + return returnFeature; + }, + /** + * [사용]관리번호로 피처검색 + */ + getFeatureByFtrIdn: function (typeName, ftrIdn) { + let returnFeature; + this.map.getLayers().forEach(function (layer) { + let layerTypeName = layer.get('typeName'); + if (layerTypeName === typeName) { + layer + .getSource() + .getFeatures() + .forEach(feature => { + let featureFtrIdn = feature.get('ftrIdn'); + if (featureFtrIdn === ftrIdn) { + returnFeature = feature; + } + }); + } + }); + return returnFeature; + }, + /** + * [사용]맵 이벤트 추가 + */ + addMapEvent: function (evtNm, id, callback) { + if (id) { + let evt = this.map.on(evtNm, callback); + evt.listener.id = id; + } + }, + /** + * [사용]맵 이벤트 삭제 + */ + removeMapEvent: function (evtNm, id) { + if (id) { + let listeners = this.map.listeners_[evtNm]; + for (let i in listeners) { + let listenter = listeners[i]; + if (listenter.id == id) { + listeners.splice(i, 1); + } + } + } + }, + /** + * [사용]마커 그리기 추가 + */ + drawMarker: function (layer, callback) { + let map = this.map; + let drawSource = layer.getSource(); + let draw = new Draw({ + type: 'Point', + source: drawSource, + condition: function (evt) { + return true; + }, + }); + map.addInteraction(draw); + draw.on('drawend', function (event) { + callback(event); + map.removeInteraction(draw); + }); + }, + /** + * [사용]피처 삭제 + */ + deleteFeature: function (layerId, featureId) { + if (!layerId || !featureId) return; + let layer = this.getLayerById(layerId); + if (layer != null) { + let feature = layer.getSource().getFeatureById(featureId); + if (feature != null) { + layer.getSource().removeFeature(feature); + } + } + }, + /** + * [사용]레이어 + */ + fitExtent: function (extent, zoom) { + let map = this.map; + let mapSize = map.getSize(); + if (!zoom) { + zoom = this.getZoom(); + } + if (extent) { + //map.getView().fit(extent, { size: [mapSize[0], mapSize[1]] }); + this.map.getView().fit(extent, { + maxZoom: 18, // 너무 확대되지 않도록 최대 줌 제한 + duration: 500, // 부드러운 애니메이션 효과 (밀리초 단위) + }); + } + this.setZoom(zoom); + }, + getGeomLineOfFeature: function (feature) { + if (!feature) { + return; + } + let geometry = feature.getGeometry(); + let type = geometry.getType(); + if (!(type == 'LineString' || type == 'MultiLineString')) { + return; + } + let geomLienString = geometry; + if (type != 'LineString') { + //lienString = geometry.getLineString(); + } + return geomLienString; + }, + getCenterGeomLineOfFeature: function (feature) { + if (!feature) { + return; + } + let center = []; + let geomLienString = this.getGeomLineOfFeature(feature); + let coords = geomLienString.getCoordinates(); + let coordCnt = coords.length; + if (coordCnt == 1) { + coords = coords[0]; + } + //라인 한개 + if (coordCnt == 2) { + return geomLienString; + } + //멀티라인 + let centerCnt = Math.floor(coordCnt / 2); + let lineCoordinates = []; + lineCoordinates.push(coords[centerCnt - 1]); + lineCoordinates.push(coords[centerCnt]); + geomLienString = new LinearRing(lineCoordinates); + return geomLienString; + }, + getCenterOfLineGeom: function (geom) { + let center = []; + let extent = geom.getExtent(); + let x = extent[0] + (extent[2] - extent[0]) / 2; + let y = extent[1] + (extent[3] - extent[1]) / 2; + center.push(x); + center.push(y); + return center; + }, + /** + * 1 -> 01 두자리수로 변환 + */ + fillZeroToZoomLevel: function (n, digits) { + let zero = ''; + n = n.toString(); + if (digits > n.length) { + for (let i = 0; digits - n.length > i; i++) { + zero += '0'; + } + } + return zero + n; + }, + convertGeoJsonToFeature: function (data, dataProjection) { + let format = new GeoSJON(); + let feature = format.readFeature(data, { + dataProjection: dataProjection || GisApp.TO_DATA_PROJ, + featureProjection: GisApp.TO_PROJ, + }); + return feature; + }, + convertGeoJsonToFeatures: function (data, dataProjection) { + let format = new GeoSJON(); + const features = []; + for (let obj of data) { + let feature = null; + try { + feature = format.readFeature(obj, { + dataProjection: dataProjection || GisApp.TO_DATA_PROJ, + featureProjection: GisApp.TO_PROJ, + }); + } catch (error) { + console.log(obj); + } + features.push(feature); + } + return features; + }, + + /** + * [사용]WKT포맷 Geometry로 feature 생성 + */ + convertWktToFeature: function (wtkGeom, dataProjection) { + let format = new WKT(); + let feature = format.readFeature(wtkGeom, { + dataProjection: dataProjection || GisApp.TO_DATA_PROJ, + featureProjection: GisApp.TO_PROJ, + }); + return feature; + }, + + /** + * [사용]geometry를 WKT포맷으로 변경 + */ + convertGeomToWkt: function (geometry) { + let format = new WKT(); + let wktGeom = format.writeGeometry(geometry, { + dataProjection: GisApp.TO_DATA_PROJ, + featureProjection: GisApp.TO_PROJ, + }); + return wktGeom; + }, + /** + * [사용]레이어 전체 표시 컨트롤 + */ + setAllLayersOnMap: function (visible) { + this.map.getLayers().forEach(function (el) { + if (el !== null && el.get('id') !== null) { + let layerId = el.get('id'); + if ('base' !== layerId && 'satellite' !== layerId && 'minimap-base' !== layerId) { + el.setVisible(visible); + } + } + }); + }, + fitExtentZoomFeatures: function (features, zoom, paddingX = 0, paddingY = 0) { + if (!features.length) return; + // Feature들의 경계를 계산하여 extent 생성 + const extent = this.getExtentFeatures(features); + if (zoom) { + //const paddingX = 0; // X축 이동량 (양수는 오른쪽, 음수는 왼쪽) + //const paddingY = -10000; // Y축 이동량 (양수는 아래쪽, 음수는 위쪽) + extent[0] -= paddingX; + extent[1] -= paddingY; + this.fitExtent(extent, zoom); + } else { + this.map.getView().fit(extent, { + maxZoom: 18, // 너무 확대되지 않도록 최대 줌 제한 + duration: 500, // 부드러운 애니메이션 효과 (밀리초 단위) + }); + } + }, + getExtentFeatures: function (features) { + if (!features.length) return; + let extent = []; + const allCoordinates = features + .map(feature => { + const geometry = feature.getGeometry(); + if (!geometry || typeof geometry.getCoordinates !== 'function') { + return null; // geometry가 없거나 getCoordinates() 메서드가 없으면 null 반환 + } + return geometry.getCoordinates(); + }) + .filter(coords => coords !== null); + function extractCoordinatePairs(arr) { + let result = []; + + function recursiveFlatten(subArray) { + if (!Array.isArray(subArray)) return; + + for (let item of subArray) { + if (Array.isArray(item) && item.length === 2 && typeof item[0] === 'number' && typeof item[1] === 'number') { + // [숫자, 숫자] 형태의 배열이면 추가 + result.push(item); + } else { + // 그렇지 않다면 재귀 호출 + recursiveFlatten(item); + } + } + } + + recursiveFlatten(arr); + return result; + } + + const flattenCoord = extractCoordinatePairs(allCoordinates); + extent = boundingExtent(flattenCoord); + return extent; + }, +}; + +/** + * [사용]피처 레이어 검색 + */ +Feature.prototype.getLayer = function () { + let map = GisApp.Module.core.map; + let this_ = this, + layer_; + let sameFeature = function (feature) { + return this_ === feature ? true : false; + }; + map.getLayers().forEach(function (layer) { + let source = layer.getSource(); + if (source instanceof VectorSource) { + let features = source.getFeatures(); + if (features.length > 0) { + let found = features.some(sameFeature); + if (found) { + layer_ = layer; + } + } + } + }); + return layer_; +}; + +export default GisApp; diff --git a/packages/common/business_components/src/ui/map/lib/gis.edit.js b/packages/common/business_components/src/ui/map/lib/gis.edit.js new file mode 100644 index 000000000..ce3fae555 --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/gis.edit.js @@ -0,0 +1,521 @@ +import GisApp from './gis.core'; +import Draw from 'ol/interaction/Draw.js'; +import Modify from 'ol/interaction/Modify.js'; +import Collection from 'ol/Collection.js'; +import Snap from 'ol/interaction/Snap.js'; +import { click, always } from 'ol/events/condition'; +import UndoRedo from 'ol-ext/interaction/UndoRedo.js'; +import Transform from 'ol-ext/interaction/Transform.js'; +import { gAlert } from './util'; +import VectorSource from 'ol/source/Vector.js'; +import VectorLayer from 'ol/layer/Vector'; + +const GisOlEditLayer = function (options) { + this.initialize(options); +}; + +GisOlEditLayer.prototype = { + defaults: {}, + editFlag: false, + draw: null, + modify: null, + snap: null, + undoredo: null, + transform: null, + drawType: { + point: 'Point', + line: 'MultiLineString', + poligon: 'MultiPolygon', + }, + selectedLayerId: null, //셀렉트 박스에서 선택된 레이어 ID + targetElement: null, + editFeatureInfo: {}, + addFeatureNo: 1, + initialize: function (options) { + Object.assign(this.defaults, options); + }, + /** + * [공통] Interaction 추가 + */ + addInteration: function () { + this.setUndoInteraction(); + }, + /** + * [공통] Interaction 삭제 + */ + removeInteraction: function () { + this.removeDraw(); + this.removeModify(); + this.removeSnap(); + this.clearUndoRedoFeature(); + }, + /** + * [공통] Interaction Active 컨트롤 + */ + setInteractionActive: function (active) { + this.setDrawActive(active); + this.setModifyActive(active); + this.setActiveUndo(active); + }, + /** + * [그리기] 기능 추가 + */ + setDraw: function (lyrId) { + if (!lyrId) return '레이어를 선택해주십시오.'; + const core = GisApp.Module.core; + const layer = core.getLayerById(lyrId); + const layerCode = GisApp.layerCode[lyrId]; + if (!layer) return '트리에서 레이어 체크 후 추가 가능합니다.'; + const lyrTy = layerCode.gobjTy; + + //초기화 + this.removeModify(); + this.removeTransform(); + if (GisApp.Module.select) { + GisApp.Module.select.setActive(false); + } + + const originalSource = layer.getSource(); + const drawSource = new VectorSource({ + features: originalSource.getFeatures(), // 기존 feature들 복사 + }); + + // 새로운 임시 레이어 추가 (draw 전용) + const drawLayer = new VectorLayer({ + source: drawSource, + minZoom: 1, // 이 레이어만 minZoom을 1로 설정 + style: layer.getStyle(), + }); + core.map.addLayer(drawLayer); + + //그리기 기능 추가 + const me = this; + + if (me.draw) { + core.map.removeInteraction(me.draw); + } + const drawType = this.drawType[lyrTy]; + this.draw = new Draw({ + type: drawType, + source: drawSource, + condition: function (evt) { + return true; + }, + }); + core.map.addInteraction(this.draw); + + //그리기 기능 이벤트 + this.draw.on('drawstart', function (event) {}); + + const drawFeatureCallback = function (event) { + me.setActiveUndo(true); + const drawFeature = event.feature; + drawFeature.setId(lyrId + '_feature_' + me.addFeatureNo++); + + const properties = {}; + properties['lyr_id'] = lyrId; + drawFeature.setProperties(properties); + + me.setAddFeatureUndo(layer, drawFeature); + me.setActiveUndo(false); + }; + + this.draw.on('drawend', function (event) { + drawFeatureCallback(event); + }); + + /*drawSource.listeners_.addfeature = null; + let addfeatureCallback = function(event){ + } + drawSource.on('addfeature', addfeatureCallback);*/ + }, + /** + * [그리기] 기능 제거 + */ + removeDraw: function () { + let core = GisApp.Module.core; + core.map.removeInteraction(this.draw); + if (GisApp.Module.select) { + GisApp.Module.select.setActive(true); + } + }, + /** + * [그리기] 기능 활성화 + */ + setDrawActive: function (active) { + if (this.draw) { + this.draw.setActive(active); + if (!active) { + /* $('#selectEditLayer').css('display', 'none'); + $('.btn_edit_add').removeClass('active'); */ + } + } + }, + /** + * [히스토리] UndoInteraction 추가 + */ + setUndoInteraction: function () { + let core = GisApp.Module.core; + if (!this.undoredo) { + this.undoredo = new UndoRedo(); + core.map.addInteraction(this.undoredo); + } + }, + /** + * [히스토리] UndoInteraction 제거 + */ + removeUndoInteraction: function () { + let core = GisApp.Module.core; + if (this.undoredo) { + core.map.removeInteraction(this.undoredo); + this.undoredo = null; + } + }, + /** + * [히스토리] UndoInteraction 활성화 + */ + setActiveUndo: function (active) { + if (this.undoredo) { + this.undoredo.setActive(active); + } + }, + /** + * [히스토리] 피처 추가 + */ + setAddFeatureUndo: function (layer, addFeature) { + if (this.undoredo) { + let obj = {}; + obj.target = layer.getSource(); + obj.feature = addFeature; + obj.type = 'addfeature'; + obj.feature.set('uploadType', 'addFeature'); + this.undoredo._onAddRemove(obj); + } + }, + /** + * [히스토리] 피처 삭제 + */ + setRemoveFeatureUndo: function (layer, removeFeature) { + if (this.undoredo) { + let obj = {}; + obj.target = layer.getSource(); + obj.feature = removeFeature; + obj.type = 'removefeature'; + if (obj.feature.get('uploadType') !== 'addFeature') { + obj.feature.set('uploadType', 'removeFeature'); + } + this.undoredo._onAddRemove(obj); + } + }, + /** + * [히스토리] 이전 + */ + undo: function () { + if (this.undoredo) { + if (this.undoredo.hasUndo() === 0) { + gAlert('INFO', 'NO_PREV_HISTORY'); + return; + } + this.setActiveUndo(true); + this.undoredo.undo(); + this.setActiveUndo(false); + } + }, + /** + * [히스토리] 다음 + */ + redo: function () { + if (this.undoredo) { + if (this.undoredo.hasRedo() === 0) { + gAlert('INFO', 'NO_NEXT_HISTORY'); + return; + } + this.setActiveUndo(true); + this.undoredo.redo(); + this.setActiveUndo(false); + } + }, + /** + * [히스토리] 초기화 + */ + clearUndoRedo: function () { + if (this.undoredo) { + const undoStack = this.undoredo._undoStack.array_; + for (let i in undoStack) { + const undo = undoStack[i]; + if (undo.feature) { + undo.feature.set('uploadType', null); + } + } + this.undoredo.clear(); + } + }, + /** + * [히스토리] 피처 초기화 + */ + clearUndoRedoFeature: function () { + if (this.undoredo) { + this.setActiveUndo(true); + const undoStack = [...this.undoredo._undoStack.array_]; + for (let i in undoStack) { + const undo = undoStack[i]; + if (undo.feature) { + this.undoredo.undo(); + } + } + this.setActiveUndo(false); + this.undoredo.clear(); + } + }, + /** + * [삭제] 피처 삭제 + */ + deleteFeature: function () { + //초기화 + this.removeDraw(); + this.removeModify(); + this.removeTransform(); + + //삭제 기능 실행 + let me = this; + let core = GisApp.Module.core; + let select = GisApp.Module.select; + select.getSelectedFeatures().forEach(function (selectedFeature) { + let selectedFeatureId = selectedFeature.getId(); + let layers = core.map.getLayers(); + for (let i in layers.array_) { + let layer = layers.array_[i]; + if (layer.get('mdfcnYn') === 'Y' && select.getVisible(layer)) { + layer + .getSource() + .getFeatures() + .forEach(function (feature) { + let featureId = feature.getId(); + if (selectedFeatureId === featureId) { + me.setActiveUndo(true); + feature.setProperties({ lyr_id: layer.get('id') }); + layer.getSource().removeFeature(feature); + me.setRemoveFeatureUndo(layer, feature); + me.setActiveUndo(false); + } + }); + } + } + }); + }, + /** + * [수정] 피처 수정 기능 셋팅 + */ + setModify: function () { + //초기화 + this.removeDraw(); + this.removeTransform(); + + //수정기능 추가 + let me = this; + let core = GisApp.Module.core; + let select = GisApp.Module.select; + let selectedFeatures = select.getSelectedFeatures(); + + this.modify = new Modify({ + features: selectedFeatures, + }); + core.map.addInteraction(this.modify); + + select.selectInteraction.on('change:active', function () { + selectedFeatures.forEach(function (each) { + selectedFeatures.remove(each); + }); + }); + + this.modify.on('modifystart', function (event) {}); + + this.modify.on('modifyend', function (event) { + let features = event.features.getArray(); + for (let i = 0; i < features.length; i++) { + let feature = features[i]; + let layer = select.selectInteraction.getLayer(feature); + feature.setProperties({ lyr_id: layer.get('id') }); + } + }); + }, + /** + * [수정] 피처 수정 기능 제거 + */ + removeModify: function () { + if (this.modify) { + let core = GisApp.Module.core; + core.map.removeInteraction(this.modify); + this.modify = null; + } + }, + /** + * [수정] 기능 활성화 + */ + setModifyActive: function (active) { + if (this.modify) { + this.modify.setActive(active); + } + }, + /** + * [크기조절] 피처 크기조절 기능 셋팅 + */ + setTransform: function () { + //초기화 + this.removeDraw(); + this.removeModify(); + + let me = this; + let core = GisApp.Module.core; + let select = GisApp.Module.select; + + this.transform = new Transform({ + enableRotatedTransform: false, + addCondition: click, + filter: function (f, l) { + return f.getGeometry().getType() !== 'Point'; + }, + // layers: [vector], + hitTolerance: select.defaults.hitTolerance, + translateFeature: true, + scale: true, + rotate: true, + keepAspectRatio: always, + translate: true, + stretch: true, + }); + this.transform.on(['rotateend', 'translateend', 'scaleend'], function (event) { + let features = event.features.getArray(); + for (let i = 0; i < features.length; i++) { + let feature = features[i]; + let layer = feature.getLayer(); + feature.setProperties({ lyr_id: layer.get('id') }); + } + }); + + core.map.addInteraction(this.transform); + }, + /** + * [크기조절] 피처 크기조절 기능 제거 + */ + removeTransform: function () { + if (this.transform) { + let core = GisApp.Module.core; + core.map.removeInteraction(this.transform); + this.transform = null; + } + }, + /** + * [크기조절] 피처 크기조절 기능 활성화 + */ + setTransformActive: function (active) { + if (this.transform) { + this.transform.setActive(active); + } + }, + /** + * [스냅] 기능 추가 + */ + setSnap: function (active) { + //초기화 + this.removeSnap(); + + //스냅 기능 추가 + let core = GisApp.Module.core; + let select = GisApp.Module.select; + core.map.removeInteraction(this.snap); + let features = []; + let layers = core.map.getLayers(); + for (let i in layers.array_) { + let layer = layers.array_[i]; + if (select.getVisible(layer) && layer.get('type') !== 'base') { + if (layer.getSource().getFeatures().length > 0) { + features = features.concat(layer.getSource().getFeatures()); + } + } + } + let snapCollection = new Collection(features); + this.snap = new Snap({ + features: snapCollection, + pixelTolerance: 30, + }); + core.map.addInteraction(this.snap); + }, + /** + * [스냅] 기능 제거 + */ + removeSnap: function () { + if (this.snap) { + let core = GisApp.Module.core; + core.map.removeInteraction(this.snap); + this.snap = null; + } + }, + /** + * [스냅] 스냅에 피처 추가 + */ + addSnap: function (layers) { + let me = this; + if (me.snap) { + for (let i in layers) { + let source = layers[i].getSource(); + source.listeners_.addfeature = null; + source.on('addfeature', function (event) { + me.snap.addFeature(event.feature); + }); + } + } + }, + /** + * [스냅] 기능 활성화 + */ + setSnapActive: function (active) { + if (this.snap) { + this.snap.setActive(active); + } + }, + + /** + * [저장] 피처 수정 정보 셋팅 + */ + setEditFeatureInfo: function (flag, feature) { + const featureId = feature.getId(); + const properties = feature.getProperties(); + + //신규 추가 후 삭제시에 editFeatureInfo에서 제거 + if (properties.uploadType === 'addFeature' && flag === 'delete') { + delete this.editFeatureInfo[featureId]; + return; + } + + //좌표값 WKT포멧으로 변경 + const pGeom = GisApp.Module.core.convertFeatureGeomToWKT(feature); + const param = { + flag, + ...properties, + geom: pGeom, + }; + this.editFeatureInfo[featureId] = param; + }, + getEditFeatureInfo: function () { + const undoStack = this.undoredo._undoStack.array_; + this.editFeatureInfo = {}; + for (let i in undoStack) { + const undo = undoStack[i]; + if (undo.feature) { + const feature = undo.feature; + const properties = feature.getProperties(); + if (properties.uploadType === 'addFeature') { + this.setEditFeatureInfo('insert', feature); + } else if (properties.uploadType === 'removeFeature') { + this.setEditFeatureInfo('delete', feature); + } else if (undo.type === 'changegeometry') { + this.setEditFeatureInfo('update', feature); + } + } + } + return this.editFeatureInfo; + }, +}; + +export default GisOlEditLayer; diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js new file mode 100644 index 000000000..3e0f6d1a1 --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -0,0 +1,557 @@ +import Feature from 'ol/Feature.js'; +import GeoJSON from 'ol/format/GeoJSON.js'; +import Point from 'ol/geom/Point.js'; +import VectorLayer from 'ol/layer/Vector'; +import VectorImageLayer from 'ol/layer/VectorImage.js'; +import { all, bbox } from 'ol/loadingstrategy'; +import { transform } from 'ol/proj'; +import Cluster from 'ol/source/Cluster.js'; +import VectorSource from 'ol/source/Vector.js'; +import VectorTile from 'ol/source/VectorTile.js'; +import CircleStyle from 'ol/style/Circle.js'; +import Fill from 'ol/style/Fill.js'; +import Icon from 'ol/style/Icon.js'; +import Stroke from 'ol/style/Stroke.js'; +import Style from 'ol/style/Style.js'; +import Text from 'ol/style/Text.js'; +import { createXYZ } from 'ol/tilegrid'; +import GisApp from './gis.core.js'; +import GisOlStyle from './gis.style.js'; +import Tile from 'ol/layer/Tile.js'; +import TileWMS from 'ol/source/TileWMS.js'; +import gisCore from './gis.core.js'; + +/** + * GisApp.layer + * @requires gis.core.js + * @requires gis.style.js + */ +const GisOlLayer = function (options) { + this.initialize(options); +}; + +GisOlLayer.prototype = { + layers: [], + cesiumLayers: [], + defaults: {}, + storageNm: 'hasudo:', + defaultLayerOptions: { + name: '', + zIndex: 2, + opacity: 1, + flterSql: null, + minZoom: '15', + }, + initialize: function (options) { + Object.assign(this.defaults, options); + this.gisStyle = new GisOlStyle(); + GisApp.Module.style = this.gisStyle; + }, + gisStyle: null, + /** + * [사용]빈 레이어 생성 + */ + makeLayer: function (options) { + let layerOptions = { + ...this.defaultLayerOptions, + ...options, + minZoom: options?.minViewSize ? options?.minViewSize : options?.minZoom, + zIndex: options?.drawOrdr ? options?.drawOrdr : 1, + }; + let returnLayer = new VectorImageLayer(layerOptions); + this.layers.push(returnLayer); + return returnLayer; + }, + /** + * gisStyle 반환 + */ + getGisStyle: function () { + return this.gisStyle; + }, + /** + * [사용]WFS 벡터 소스 반환 + */ + getVectorSourceGeoserver: function (options) { + const url = GisApp.GEOSERVER_URL; + const toProj = GisApp.TO_PROJ; + const lyrTyNm = options.lyrTyNm; + const params = {}; + params['TYPENAME'] = this.storageNm + lyrTyNm; + params['SERVICE'] = 'WFS'; + params['VERSION'] = '1.1.0'; + params['REQUEST'] = 'GetFeature'; + params['SRS'] = toProj; + params['OUTPUTFORMAT'] = 'application/json'; + if (options?.flterSql && options?.flterSql?.trim() !== 'null') { + params['CQL_FILTER'] = options?.flterSql; + } + const strParams = new URLSearchParams(params).toString(); + let source = new VectorSource({ + format: new GeoJSON(), + url: function (extent) { + return url + '?' + strParams; + }, + strategy: all, + }); + return source; + }, + /** + * [사용]WFS 벡터 소스 반환 + */ + getVectorTileSource: function (options) { + let url = GisApp.GEOSERVER_URL; + let toProj = GisApp.TO_PROJ; + let params = {}; + params['TYPENAME'] = this.storageNm + options.typeName; + params['SERVICE'] = 'WFS'; + params['VERSION'] = '1.1.0'; + params['REQUEST'] = 'GetFeature'; + params['SRS'] = toProj; + params['OUTPUTFORMAT'] = 'application/json'; + let strParams = new URLSearchParams(params).toString(); + + let source = new VectorTile({ + format: new GeoJSON(), + + tileUrlFunction: function (tileCoord, pixelRatio, projection) { + let filterStr = '&CQL_FILTER=bbox(GEOM,' + source.getTileGrid().getTileCoordExtent(tileCoord).join(',') + ",'" + projection.getCode() + "')"; + if (options.flterSql) { + filterStr += ' AND ' + options.flterSql; + } + let paramWithUrl = url + '?' + strParams + filterStr; + return paramWithUrl; + }, + tileGrid: createXYZ, + }); + + return source; + }, + /** + * [사용]WFS 레이어 생성 + */ + getWfsLayer: function (option) { + option = option || {}; + //레이어 생성 + let returnLayer = this.makeLayer(option); + //레이어 소스 생성 + let source = this.getVectorSourceGeoserver(option); + returnLayer.setSource(source); + //스타일 추가 + returnLayer.setStyle(this.gisStyle.getLayerStyleFunction(returnLayer, option)); + returnLayer.set('id', option.id + ''); + return returnLayer; + }, + /** + * [사용]WMS 레이어 생성 + */ + getWmsLayer: function (options) { + let layerOptions = Object.assign({}, this.defaultLayerOptions, options); + let url = GisApp.GEOSERVER_WMS_URL; + let toProj = GisApp.TO_PROJ; + const lyrTyNm = options.lyrTyNm; + let params = {}; + params['LAYERS'] = this.storageNm + lyrTyNm; + params['SERVICE'] = 'WMS'; + params['VERSION'] = '1.1.1'; + params['REQUEST'] = 'GetMap'; + params['SRS'] = toProj; + params['FORMAT'] = 'image/png'; + params['TILED'] = true; + const wmsLayer = new Tile({ + ...layerOptions, + id: layerOptions?.id, + source: new TileWMS({ + url, + params, + serverType: 'geoserver', + }), + }); + wmsLayer.setZIndex(options.draw0rdr); + wmsLayer.setMinZoom(options.minViewSize); + this.layers.push(wmsLayer); + return wmsLayer; + }, + /** + * [사용]WFS 레이어 생성 + */ + getWfsEmptyLayer: function (option) { + option = option || {}; + //레이어 생성 + let returnLayer = this.makeLayer(option); + let vectorSource = new VectorSource({ + strategy: all, + }); + returnLayer.setSource(vectorSource); + //스타일 추가 + returnLayer.setStyle(this.gisStyle.getLayerStyle(returnLayer, option)); + returnLayer.set('id', option.id + ''); + let map = GisApp.Module.core.map; + map.addLayer(returnLayer); + return returnLayer; + }, + /** + * 피처 리스트로 레이어 생성 + */ + getLayerByFeature: function (feature, layerOptions, layerStyle) { + if (!layerOptions || !layerOptions.id) { + return null; + } + let vectorSource = new VectorSource({ + strategy: all, + }); + if (feature) { + if (Object.prototype.toString.call(feature).slice(8, -1).indexOf('Object') > -1) { + vectorSource.addFeature(feature); + } else { + vectorSource.addFeatures(feature); + } + } + GisApp.Module.core.deleteLayer(layerOptions.id); + layerOptions.source = vectorSource; + let layer = new VectorImageLayer(layerOptions); + layer.setStyle(layerStyle); + layer.set('id', layerOptions.id + ''); + return layer; + }, + /** + * 피처 하이라이트 + */ + highLightFeature: function (hlFeature, layerOptions, styleOptions, customStyleFunction) { + const color = styleOptions.color || 'rgba(135, 116, 243, 0.4)'; + const width = styleOptions.width || 10; + const featureKey = styleOptions.featureKey || ''; + const fitExtent = layerOptions.fitExtent || 'true'; + + const styleFunction = function (feature, resolution) { + const featureStyle = feature.getStyle(); + if (featureStyle) return null; + const style = new Style({ + image: new CircleStyle({ + fill: new Fill({ + color: color, + }), + radius: width, + }), + stroke: new Stroke({ + color: color, + width: width, + }), + }); + + const getFeatureText = function (feature) { + if (featureKey) { + let text = feature.get(featureKey); + if (text) return text; + } + return ''; + }; + const text = new Text({ + stroke: new Stroke({ + color: 'white', + width: 2, + }), + fill: new Fill({ + color: 'black', + }), + font: '12px Verdana', + text: getFeatureText(feature), + }); + style.setText(text); + return style; + }; + let actualStyleFunction = customStyleFunction || styleFunction; + GisApp.Module.core.deleteLayer(layerOptions.id); + let layer = this.getLayerByFeature(hlFeature, layerOptions, actualStyleFunction); + if (hlFeature && hlFeature.length > 0) { + let map = GisApp.Module.core.map; + map.addLayer(layer); + if (fitExtent === 'true') { + GisApp.Module.core.fitExtent(layer.getSource().getExtent()); + } + } + return layer; + }, + /** + * 마커 레이어 맵에 추가 + */ + addMarkerLayer: function (lat, lon, fromProj, layerOptions) { + let p = transform([Number(lon), Number(lat)], fromProj, GisApp.TO_PROJ); + let feature = new Feature({ + geometry: new Point(p), + }); + let layer = this.getMarkerLayer(layerOptions); + layer.getSource().addFeature(feature); + let map = GisApp.Module.core.map; + map.addLayer(layer); + GisApp.Module.core.fitExtent(feature.getGeometry().getExtent()); + return layer; + }, + /** + * 마커 레이어 생성 + */ + getMarkerLayer: function (layerOptions) { + let imgSrc = GisApp.CONTEXT_PATH + 'images/marker/marker-icon.png'; + let style = new Style({ + image: new Icon({ + anchor: [0.5, 46], + anchorXUnits: 'fraction', + anchorYUnits: 'pixels', + src: imgSrc, + }), + }); + let layer = this.getLayerByFeature(null, layerOptions, style); + return layer; + }, + /** + * 피처 생성 + */ + getFeatureCoord: function (coord) { + let feature = new Feature({ + geometry: new Point(coord), + }); + return feature; + }, + /** + * 피처 생성(lat,lon) + */ + getFeatureLonLat: function (lat, lon) { + let p = transform([Number(lon), Number(lat)], GisApp.FROM_PROJ, GisApp.TO_PROJ); + let feature = new Feature({ + geometry: new Point(p), + }); + return feature; + }, + addPolygonLayer: function (features, option) { + const map = GisApp.Module.core.map; + option = Object.assign( + { + gobjTy: 'lineLabelPoligon', + minZoom: option?.minZoom || '10', + lblAtrbNm: option?.lblAtrbNm || 'fclty_nm', + lnWdth: option?.lnWdth || '2', + lnColor: option?.lnColor || 'rgba(0, 0, 0, 1)', + polgnColor: option?.polgnColor || 'rgba(255, 0, 0, 0.2)', + clickPsblYn: option?.clickPsblYn || 'N', + fondNm: '18px "맑은 고딕",Malgun Gothic', + }, + option + ); + features[0].set('overLayId', option?.overlayId || null); + features[0].set('html', option?.html || null); + //레이어 생성 + let vectorLayer = this.makeLayer(option); + //레이어 소스 생성 + const vectorSource = new VectorSource({}); + vectorSource.addFeatures(features); + vectorLayer.setSource(vectorSource); + //스타일 추가 + vectorLayer.setStyle(this.gisStyle.getLayerStyleFunction(vectorLayer, option)); + vectorLayer.set('id', option?.id + ''); + GisApp.Module.core.deleteLayer(option?.id); + map.addLayer(vectorLayer); + }, + addLayerByLyrTyNm: function (id, lyrTyNm, addQurryFlter) { + const map = GisApp.Module.core.map; + const layerCode = GisApp.layerCode; + const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm); + matched['id'] = id; + let layer = GisApp.Module.core.getLayerById(matched?.id); + if (layer) { + layer.setVisible(true); + } else { + const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || ''); + matched['flterSql'] = queryFlter; + layer = this.getWfsLayer(matched); + GisApp.Module.core.deleteLayer(id); + map.addLayer(layer); + } + }, + /** + * 클러스터 레이어 맵에 추가 + */ + addClusterLayer: function (features, { layerId, color }) { + const map = GisApp.Module.core.map; + const vectorSource = new VectorSource({}); + vectorSource.addFeatures(features); + const clusterLayerId = layerId + 'Cluster'; + const vectorLayerId = layerId + 'Vector'; + // 클러스터 레이어 생성 + const clusterLayer = new VectorLayer({ + id: clusterLayerId, + source: new Cluster({ + distance: 100, // 클러스터링 거리 + source: vectorSource, + geometryFunction: function (feature) { + let geometry = feature.getGeometry(); + let type = geometry.getType(); + let coordinates; + if (type === 'Point') { + coordinates = geometry.getCoordinates(); + } else if (type === 'LineString' || type === 'MultiLineString') { + let totalX = 0; + let totalY = 0; + let totalPoints = 0; + geometry.getCoordinates().forEach(line => { + line.forEach(point => { + totalX += point[0]; + totalY += point[1]; + totalPoints++; + }); + }); + coordinates = [totalX / totalPoints, totalY / totalPoints]; + } + return new Point(coordinates); + }, + }), + style: feature => { + const clusterSize = feature.get('features').length; + return new Style({ + image: new CircleStyle({ + radius: 10, + fill: new Fill({ color }), + }), + stroke: new Stroke({ + color, + width: 10, + }), + text: new Text({ + text: clusterSize.toString(), + fill: new Fill({ + color: 'white', + }), + stroke: new Stroke({ + color: 'black', + width: 4, + }), + }), + }); + }, + }); + + const vectorLayer = new VectorLayer({ + id: vectorLayerId, + zIndex: 1, + source: vectorSource, + style: new Style({ + image: new CircleStyle({ + radius: 10, + fill: new Fill({ color }), + }), + stroke: new Stroke({ + color, + width: 10, + }), + }), + }); + GisApp.Module.core.deleteLayer(clusterLayerId); + GisApp.Module.core.deleteLayer(vectorLayerId); + map.addLayer(clusterLayer); + map.addLayer(vectorLayer); + function handleZoomEnd() { + const findClusterLayer = map + ?.getLayers() + ?.getArray() + .find(function (layer) { + return layer.get('id') === clusterLayerId; + }); + const findVectorLayer = map + ?.getLayers() + ?.getArray() + .find(function (layer) { + return layer.get('id') === vectorLayerId; + }); + if (!findClusterLayer) { + GisApp.Module.core.deleteLayer(vectorLayerId); + } + if (!findVectorLayer) { + GisApp.Module.core.deleteLayer(vectorLayerId); + } + const zoomLevel = map.getView().getZoom(); + if (zoomLevel >= 18) { + vectorLayer.setVisible(true); + clusterLayer.setVisible(false); + } else { + clusterLayer.setVisible(true); + vectorLayer.setVisible(false); + } + if (!findClusterLayer && !findVectorLayer) { + map.getView().un('change:resolution', handleZoomEnd); + } + } + // 줌 이벤트 핸들러 등록 + map.getView().un('change:resolution', handleZoomEnd); + map.getView().on('change:resolution', handleZoomEnd); + handleZoomEnd(); + }, + setLayersToCesium: function (ol3dInstance) { + let me = this; + //const { Cesium } = window; + const cesiumScene = ol3dInstance.getCesiumScene(); // Cesium Scene 가져오기 + me.cesiumLayers.forEach(cesiumLayer => { + cesiumScene.imageryLayers.remove(cesiumLayer); + }); + const layers = GisApp.Module.core.map + .getAllLayers() + .filter(layer => layer.get('type') !== 'base') + .sort((a, b) => { + const orderA = a.getProperties().drawOrdr || 0; + const orderB = b.getProperties().drawOrdr || 0; + return orderA - orderB; // 낮은 값이 먼저 오도록 정렬 + }); + + layers.forEach(layer => { + const properties = layer.getProperties(); + let cesiumLayer = null; + if (properties?.lyrTyNm) { + const visible = layer.getVisible(); + const lyrTyNm = properties.lyrTyNm; + // OpenLayers에서 레이어의 가시성 확인 + const { Cesium } = window; + const wmsUrl = GisApp.GEOSERVER_WMS_URL; + const wmsProvider = new Cesium.WebMapServiceImageryProvider({ + url: wmsUrl, + layers: GisApp.Module.layer.storageNm + lyrTyNm, // WMS 레이어 이름 + parameters: { + service: 'WMS', + version: '1.1.1', + request: 'GetMap', + format: 'image/png', + transparent: true, + styles: lyrTyNm, + }, + }); + cesiumLayer = cesiumScene.imageryLayers.addImageryProvider(wmsProvider); + cesiumLayer['properties'] = properties; + me.cesiumLayers.push(cesiumLayer); + if (properties?.visible) { + const zoomLevel = getOpenLayersZoom(cesiumScene); + cesiumLayer.show = zoomLevel >= Number(properties?.minZoom); + } else { + cesiumLayer.show = false; + } + } + }); + if (me.cesiumLayers.length > 0) { + cesiumScene._moveEndListener = () => { + const zoomLevel = getOpenLayersZoom(cesiumScene); // 대략적인 줌 계산 + me.cesiumLayers.forEach(cesiumLayer => { + const properties = cesiumLayer.properties; + if (properties?.visible) { + console.log('zoomLevel >= minZoom==', properties?.lyrTyNm, zoomLevel >= properties?.minZoom); + cesiumLayer.show = zoomLevel >= Number(properties?.minZoom); + } + }); + }; + cesiumScene.camera.moveEnd.addEventListener(cesiumScene._moveEndListener); + } + }, +}; + +function getOpenLayersZoom(cesiumScene) { + const cameraHeight = cesiumScene.camera.positionCartographic.height; + const maxResolution = 156543.03392804097; // OpenLayers 기본 해상도 (Google Mercator 기준) + const tileSize = 256; // 기본 타일 크기 + return Math.ceil(Math.log2(maxResolution / (cameraHeight / tileSize))); +} + +export default GisOlLayer; diff --git a/packages/common/business_components/src/ui/map/lib/gis.measure.js b/packages/common/business_components/src/ui/map/lib/gis.measure.js new file mode 100644 index 000000000..6fc427bed --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/gis.measure.js @@ -0,0 +1,204 @@ +import Style from 'ol/style/Style'; +import VectorLayer from 'ol/layer/Vector'; +import VectorSource from 'ol/source/Vector'; +import Fill from 'ol/style/Fill.js'; +import Stroke from 'ol/style/Stroke'; +import CircleStyle from 'ol/style/Circle'; +import { getLength, getArea } from 'ol/sphere'; +import Draw from 'ol/interaction/Draw'; +import Polygon from 'ol/geom/Polygon'; +import LineString from 'ol/geom/LineString'; +import { unByKey } from 'ol/Observable'; +import Overlay from 'ol/Overlay'; +import GisApp from './gis.core'; +//import DOMPurify from 'dompurify'; +const GisOlMeasure = function (options) { + GisApp.measure = {}; + GisApp.measure.helpTooltipElement = null; + GisApp.measure.helpTooltip = null; + GisApp.measure.sketch = null; + GisApp.measure.measureTooltipElement = null; + GisApp.measure.measureTooltip = null; + GisApp.measure.continueLineMsg = '선 그리기 종료를 위해 마우스를 더블클릭하세요.'; + GisApp.measure.Polygon = 'Polygon'; + GisApp.measure.LineString = 'LineString'; + + this.initialize(options); +}; + +GisOlMeasure.prototype = { + map: null, + draw: null, + vector: null, + type: null, + initialize: function (map) { + let me = this; + let source = new VectorSource(); + let vector = new VectorLayer({ + source: source, + style: new Style({ + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.2)', + }), + stroke: new Stroke({ + color: '#ffcc33', + width: 2, + }), + image: new CircleStyle({ + radius: 7, + fill: new Fill({ + color: '#ffcc33', + }), + }), + }), + }); + me.map = map; + me.vector = vector; + me.map.addLayer(vector); + me.map.removeInteraction(this.draw); + }, + pointerMoveHandler: function (evt) { + if (evt.dragging) { + return; + } + let helpMsg = '시작점을 선택하세요'; + if (GisApp.measure.sketch) { + helpMsg = GisApp.measure.continueLineMsg; + } + if (GisApp.measure.helpTooltipElement) { + //GisApp.measure.helpTooltipElement.innerHTML = DOMPurify.sanitize(helpMsg); + GisApp.measure.helpTooltipElement.innerHTML = helpMsg; + GisApp.measure.helpTooltip.setPosition(evt.coordinate); + GisApp.measure.helpTooltipElement.classList.remove('hidden'); + } + }, + formatLength: function (line) { + let length = getLength(line); + let output; + if (length > 1000) { + output = Math.round((length / 1000) * 100) / 100 + ' ' + 'km'; + } else { + output = Math.round(length * 100) / 100 + ' ' + 'm'; + } + return output; + }, + formatArea: function (polygon) { + let area = getArea(polygon); + let output; + if (area > 10000) { + output = Math.round((area / 1000000) * 100) / 100 + ' ' + 'km2'; + } else { + output = Math.round(area * 100) / 100 + ' ' + 'm2'; + } + return output; + }, + addInteraction: function (type) { + let me = this; + me.map.removeInteraction(me.draw); + me.map.on('pointermove', me.pointerMoveHandler); + me.type = type; + + let source = me.vector.getSource(); + me.draw = new Draw({ + source: source, + type: type, + style: new Style({ + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.2)', + }), + stroke: new Stroke({ + color: 'rgba(0, 0, 0, 0.5)', + lineDash: [10, 10], + width: 2, + }), + image: new CircleStyle({ + radius: 5, + stroke: new Stroke({ + color: 'rgba(0, 0, 0, 0.7)', + }), + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.2)', + }), + }), + }), + }); + + me.map.addInteraction(me.draw); + me.createMeasureTooltip(); + me.createHelpTooltip(); + + let listener; + me.draw.on('drawstart', function (evt) { + let tooltipCoord = evt.coordinate; + GisApp.measure.sketch = evt.feature; + listener = GisApp.measure.sketch.getGeometry().on('change', function (evt) { + let geom = evt.target; + let output; + if (geom instanceof Polygon) { + output = me.formatArea(geom); + tooltipCoord = geom.getInteriorPoint().getCoordinates(); + } else if (geom instanceof LineString) { + output = me.formatLength(geom); + tooltipCoord = geom.getLastCoordinate(); + } + //GisApp.measure.measureTooltipElement.innerHTML = DOMPurify.sanitize(output); + GisApp.measure.measureTooltipElement.innerHTML = output; + GisApp.measure.measureTooltip.setPosition(tooltipCoord); + }); + }); + + me.draw.on('drawend', function () { + GisApp.measure.measureTooltipElement.className = 'ol-tooltip ol-tooltip-static'; + GisApp.measure.measureTooltip.setOffset([0, -7]); + GisApp.measure.sketch = null; + // unset tooltip so that a new one can be created + GisApp.measure.measureTooltipElement = null; + me.createMeasureTooltip(); + unByKey(listener); + me.removeInteraction(); + }); + }, + removeInteraction: function () { + this.map.removeInteraction(this.draw); + this.map.removeOverlay(GisApp.measure.helpTooltip); + }, + removeFeature: function () { + let source = this.vector.getSource(); + let features = this.vector.getSource().getFeatures(); + for (let i in features) { + let feature = features[i]; + source.removeFeature(feature); + } + }, + createHelpTooltip: function () { + let me = this; + if (GisApp.measure.helpTooltipElement) { + GisApp.measure.helpTooltipElement.parentNode.removeChild(GisApp.measure.helpTooltipElement); + } + GisApp.measure.helpTooltipElement = document.createElement('div'); + GisApp.measure.helpTooltipElement.className = 'ol-tooltip hidden'; + GisApp.measure.helpTooltip = new Overlay({ + element: GisApp.measure.helpTooltipElement, + offset: [15, 0], + positioning: 'center-left', + }); + me.map.addOverlay(GisApp.measure.helpTooltip); + }, + createMeasureTooltip: function () { + let me = this; + if (GisApp.measure.measureTooltipElement) { + GisApp.measure.measureTooltipElement.parentNode.removeChild(GisApp.measure.measureTooltipElement); + } + GisApp.measure.measureTooltipElement = document.createElement('div'); + GisApp.measure.measureTooltipElement.className = 'ol-tooltip ol-tooltip-measure'; + GisApp.measure.measureTooltip = new Overlay({ + id: 'measure', + element: GisApp.measure.measureTooltipElement, + offset: [0, -15], + positioning: 'bottom-center', + }); + me.map.addOverlay(GisApp.measure.measureTooltip); + }, +}; + +export default GisOlMeasure; diff --git a/packages/common/business_components/src/ui/map/lib/gis.select.js b/packages/common/business_components/src/ui/map/lib/gis.select.js new file mode 100644 index 000000000..d48c67a5e --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/gis.select.js @@ -0,0 +1,407 @@ +import GisApp from './gis.core'; +import _ from 'lodash'; +import Style from 'ol/style/Style.js'; +import { click, mouseOnly, never } from 'ol/events/condition'; +import DragBox from 'ol/interaction/DragBox.js'; +import Draw from 'ol/interaction/Draw.js'; +import Select from 'ol/interaction/Select.js'; +import VectorLayer from 'ol/layer/Vector.js'; +import Overlay from 'ol/Overlay.js'; +import VectorSource from 'ol/source/Vector.js'; +import CircleStyle from 'ol/style/Circle.js'; +import Fill from 'ol/style/Fill.js'; +import Stroke from 'ol/style/Stroke.js'; +//import DOMPurify from 'dompurify'; + +const GisOlSelect = function (map, options) { + GisApp.select = {}; + GisApp.select.helpTooltipElement = null; + GisApp.select.helpTooltip = null; + GisApp.select.helpMsg = null; + this.initialize(map, options); +}; + +GisOlSelect.prototype = { + map: null, + defaults: { + hitTolerance: 10, + useAreaControl: false, + }, + selectInteraction: null, + dragBox: null, + dragPolygon: null, + selectedFeatures: [], + selectFeatureFlag: false, + initialize: function (map, options) { + this.map = map; + _.extend(this.defaults, options); + this.setInteraction(); + this.pointerMoveEventHandler(); + }, + /** + * SELECT 이벤트 등록 + */ + setInteraction: function () { + let me = this; + let map = this.map; + if (this.selectInteraction) { + this.removeInteration(); + } + let baseStyle = new Style({ + stroke: new Stroke({ + color: 'rgba(135, 116, 243, 0.4)', + width: 10, + }), + }); + + this.selectInteraction = new Select({ + style: function (feature) { + let styles = []; + let layer = feature.getLayer(); + let style = GisApp.Module.style.getLayerStyle(layer, null); + if (style) { + styles.push(style); + } + styles.push(baseStyle); + return styles; + }, + layers: function (layer) { + let clickPsblYn = layer?.get('clickPsblYn'); + return clickPsblYn === 'Y'; + }, + condition: click, + hitTolerance: me.defaults.hitTolerance, + }); + map.addInteraction(this.selectInteraction); + + this.selectedFeatures = this.selectInteraction.getFeatures(); + }, + /** + * SELECT 피처 초기화 + */ + reset: function () { + let me = this; + if (GisApp.Module.edit) { + GisApp.Module.edit.setDrawActive(false); + } + if (GisApp.Module.draw) { + GisApp.Module.draw.setDrawActive(false); + } + me.setActive(true); + me.removeHelpTooltip(); + + let map = this.map; + map.removeInteraction(me.dragBox); + map.removeInteraction(me.dragPolygon); + + this.selectInteraction.addCondition_ = never; + this.selectFeatureFlag = false; + + if (GisApp.Module.measure) { + GisApp.Module.measure.removeFeature(); + GisApp.Module.core.deleteOverlay('measure'); + } + + GisApp.Module.core.deleteLayer(GisApp.hLayerId); + GisApp.Module.core.deleteLayer(GisApp.hlLayerId); + + this.resetSelectedFeature(); + }, + /** + * 단일 SELECT 이벤트 등록 + */ + setSingleInteration: function () { + this.reset(); + this.selectInteraction.addCondition_ = click; + this.selectFeatureFlag = true; + }, + /** + * 박스 SELECT 이벤트 등록 + */ + setBoxInteration: function () { + //초기화 + this.reset(); + this.selectFeatureFlag = true; + + let me = this; + let map = this.map; + me.dragBox = new DragBox({ + condition: mouseOnly, + }); + map.addInteraction(this.dragBox); + GisApp.select.helpMsg = '직사각형 시작점을 클릭하고 드래그하세요.'; + me.createHelpTooltip(); + me.dragBox.on('boxend', function () { + let rotation = map.getView().getRotation(); + let oblique = rotation % (Math.PI / 2) !== 0; + let candidateFeatures = oblique ? [] : me.selectedFeatures; + let extent = me.dragBox.getGeometry().getExtent(); + let layers = map.getLayers(); + for (let i in layers.array_) { + let layer = layers.array_[i]; + if (layer?.get('clickPsblYn') == 'Y' && me.getVisible(layer)) { + layer.getSource().forEachFeatureIntersectingExtent(extent, function (feature) { + candidateFeatures.push(feature); + }); + } + } + + if (oblique) { + let anchor = [0, 0]; + let geometry = me.dragBox.getGeometry().clone(); + geometry.rotate(-rotation, anchor); + let extent$1 = geometry.getExtent(); + candidateFeatures.forEach(function (feature) { + let geometry = feature.getGeometry().clone(); + geometry.rotate(-rotation, anchor); + if (geometry.intersectsExtent(extent$1)) { + me.selectedFeatures.push(feature); + } + }); + } + me.selectInteraction.dispatchEvent({ + type: 'select', + selected: me.selectedFeatures, + deselected: [], + }); + map.removeInteraction(me.dragBox); + }); + + me.dragBox.on('boxstart', function () { + me.removeHelpTooltip(); + me.selectedFeatures.clear(); + }); + }, + /** + * 폴리곤 SELECT 이벤트 등록 + */ + setPolygonInteration: function () { + this.reset(); + this.selectFeatureFlag = true; + + let me = this; + let map = this.map; + + //폴리곤 레이어 생성 + let polygonSource = new VectorSource({ + useSpatialIndex: false, + }); + let polygonLayer = new VectorLayer({ + source: polygonSource, + }); + map.addLayer(polygonLayer); + + //폴리곤 draw 객체 생성 + me.dragPolygon = new Draw({ + source: polygonSource, + type: 'Polygon', + condition: mouseOnly, + }); + map.addInteraction(me.dragPolygon); + + //툴팁 추가 + GisApp.select.helpMsg = '시작점을 클릭하세요.'; + me.createHelpTooltip(); + + me.dragPolygon.on('drawstart', function () { + me.removeHelpTooltip(); + me.selectedFeatures.clear(); + }); + + me.dragPolygon.on('drawend', function (evt) { + setTimeout(function () { + let polygon = evt.feature.getGeometry(); + let layers = map.getLayers(); + for (let i in layers.array_) { + let layer = layers.array_[i]; + if (layer?.get('clickPsblYn') == 'Y' && me.getVisible(layer)) { + let features = layer.getSource().getFeatures(); + for (let j = 0; j < features.length; j++) { + if (polygon.intersectsExtent(features[j].getGeometry().getExtent())) { + me.selectedFeatures.push(features[j]); + } + } + } + } + me.selectInteraction.dispatchEvent({ + type: 'select', + selected: me.selectedFeatures, + deselected: [], + }); + map.removeInteraction(me.dragPolygon); + map.removeLayer(polygonLayer); + }, 300); + }); + return me.dragPolygon; + }, + /** + * 레이어 추가 + */ + addLayer: function (layer) { + if (layer) { + layer.set('clickPsblYn', 'Y'); + } + }, + /** + * 레이어 삭제 + */ + removeLayer: function (layer) { + if (layer) { + layer.set('clickPsblYn', 'N'); + } + }, + /** + * 선택된 객체 초기화 + */ + resetSelectedFeature: function () { + const selectedFeatures = this.selectedFeatures; + selectedFeatures.forEach(feature => { + selectedFeatures.remove(feature); + }); + }, + /** + * SELECT 이벤트 활성화 + */ + setActive: function (active) { + this.selectInteraction.setActive(active); + }, + /** + * SELECT 이벤트 삭제 + */ + removeInteration: function () { + let map = this.map; + map.removeInteraction(this.selectInteraction); + this.selectInteraction = null; + + //포인터 변경 삭제 + this.removePointerMoveEventHandler('pointerMoveForSelect'); + }, + /** + * 피처 위에 마우스 오버시 포인터 변경 + */ + pointerMoveEventHandler: function () { + let me = this; + let map = this.map; + let pointerMoveForSelect = function (evt) { + let hit = this.forEachFeatureAtPixel( + evt.pixel, + function (feature, layer) { + let returnFlag = true; + if (layer?.get('clickPsblYn') === 'N') { + returnFlag = false; + } + return returnFlag; + }, + { hitTolerance: me.defaults.hitTolerance } + ); + + if (hit && me.selectInteraction.getActive()) { + this.getTargetElement().style.cursor = 'pointer'; + } else { + this.getTargetElement().style.cursor = ''; + } + }; + map.on('pointermove', pointerMoveForSelect); + }, + /** + * 포인터 이벤트 삭제 + */ + removePointerMoveEventHandler: function (fnNm) { + GisApp.Module.core.removeMapEventFnNm('pointermove', fnNm); + }, + /** + * 선택된 피처 정보 반환(callback) + */ + getSelectedInfo: function (callback) { + let me = this; + this.selectInteraction.on('select', function (e) { + if (!e.auto) { + let features = me.selectInteraction.getFeatures(); + let layers = []; + for (let i in features.array_) { + let feature = features.array_[0]; + let layer = me.selectInteraction.getLayer(feature); + layers.push(layer); + } + callback(features.array_, layers); + } + }); + }, + /** + * 선택된 피처 정보 반환 + */ + getSelectedFeatures: function () { + return this.selectInteraction.getFeatures(); + }, + /** + * 마지막으로 선택된 피처 정보 반환 + */ + getLastSelectedFeatures: function () { + return this.selectInteraction.getFeatures(); + }, + /** + * 툴팁 생성 + */ + createHelpTooltip: function () { + let me = this; + let map = this.map; + GisApp.select.helpTooltipElement = document.createElement('div'); + GisApp.select.helpTooltipElement.className = 'ol-tooltip hidden'; + GisApp.select.helpTooltip = new Overlay({ + element: GisApp.select.helpTooltipElement, + offset: [15, 0], + positioning: 'center-left', + }); + map.addOverlay(GisApp.select.helpTooltip); + map.on('pointermove', me.pointerMoveTooltipHandler); + }, + /** + * 툴팁 삭제 + */ + removeHelpTooltip: function () { + if (GisApp.select.helpTooltipElement) { + let map = this.map; + this.removePointerMoveEventHandler('pointerMoveTooltipHandler'); + map.removeOverlay(GisApp.measure.helpTooltip); + GisApp.select.helpTooltipElement.remove(); + } + }, + /** + * 툴팁용 포인터 move 이벤트 + */ + pointerMoveTooltipHandler: function (evt) { + if (evt.dragging) { + return; + } + //GisApp.select.helpTooltipElement.innerHTML = DOMPurify.sanitize(GisApp.select.helpMsg); + GisApp.select.helpTooltipElement.innerHTML = GisApp.select.helpMsg; + GisApp.select.helpTooltip.setPosition(evt.coordinate); + GisApp.select.helpTooltipElement.classList.remove('hidden'); + }, + getVisible: function (layer) { + let maxZoom = layer.getMaxZoom(); + let minZoom = layer.getMinZoom(); + let map = this.map; + let curZoom = map.getView().getZoom(); + if (layer.getVisible() && maxZoom > curZoom && minZoom < curZoom) { + return true; + } + return false; + }, + /** + * 피처 셀렉트 + */ + selectFeature: function (feature, auto) { + let me = this; + me.selectedFeatures.clear(); + me.selectedFeatures.push(feature); + me.selectInteraction.dispatchEvent({ + type: 'select', + selected: me.selectedFeatures, + deselected: [], + auto: auto, + }); + }, +}; + +export default GisOlSelect; diff --git a/packages/common/business_components/src/ui/map/lib/gis.style.js b/packages/common/business_components/src/ui/map/lib/gis.style.js new file mode 100644 index 000000000..df9be2daa --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/gis.style.js @@ -0,0 +1,402 @@ +import GisApp from './gis.core.js'; +import Style from 'ol/style/Style.js'; +import Fill from 'ol/style/Fill.js'; +import Icon from 'ol/style/Icon.js'; +import Circle from 'ol/style/Circle.js'; +import Stroke from 'ol/style/Stroke.js'; +import Text from 'ol/style/Text.js'; +import Point from 'ol/geom/Point.js'; +import RegularShape from 'ol/style/RegularShape.js'; +import Overlay from 'ol/Overlay'; +import _ from 'lodash'; +//import DOMPurify from 'dompurify'; +import { createPortal } from 'react-dom'; +import ReactDOM from 'react-dom/client'; +import React from 'react'; + +const GisOlStyle = function () { + this.initialize(); +}; + +GisOlStyle.prototype = { + overlays: {}, + hiddenOverlays: {}, + initialize: function () { + this.layericonPath = GisApp.CONTEXT_LAYER_PATH; + }, + getRGBAToString: function (r, g, b, a) { + let rgba = 'rgba({r}, {g}, {b}, {a})'; + return rgba.replace('{r}', r).replace('{g}', g).replace('{b}', b).replace('{a}', a); + }, + getFillColor: function (layer) { + let r = layer.get('fillColorR'); + let g = layer.get('fillColorG'); + let b = layer.get('fillColorB'); + let a = layer.get('fillColorA'); + return this.getRGBAToString(r, g, b, a); + }, + getFontLineColor: function (layer) { + let r = layer.get('fontOutlineColorR'); + let g = layer.get('fontOutlineColorG'); + let b = layer.get('fontOutlineColorB'); + let a = layer.get('fontOutlineColorA'); + return this.getRGBAToString(r, g, b, a); + }, + getFontFillColor: function (layer) { + let r = layer.get('fontColorR'); + let g = layer.get('fontColorG'); + let b = layer.get('fontColorB'); + let a = layer.get('fontColorA'); + return this.getRGBAToString(r, g, b, a); + }, + point: function (layer, options, feature, resolution) { + let symbol = layer.get('iconNm'); + let maticKey = layer.get('maticKey'); + let maticVal1 = layer.get('maticVal1'); + let image; + let style = new Style({ + fill: new Fill(), + }); + + let text = this.getText(layer, feature, resolution); + style.setText(text); + + if (symbol) { + let rotation = 0; + if (feature) { + let angDir = feature.get('angDir'); + if (angDir) { + rotation = (angDir * Math.PI) / 180; + } + } + image = new Icon({ + crossOrigin: 'anonymous', + scale: 1.2, + src: this.layericonPath + symbol, + rotation: rotation, + }); + } else { + image = new Circle({ + fill: new Fill({ + color: '#4848B4', + }), + radius: 5, + }); + } + + const overlayHtml = feature.get('html'); + if (overlayHtml) { + this.setOverlay(layer, feature, overlayHtml); + } + + let baseResolution = 20; + let minScale = 0.3; + let maxScale = 1.5; + let scale = baseResolution / resolution > maxScale ? maxScale : baseResolution / resolution; + scale = Math.max(minScale, Math.min(maxScale, scale)); + image.setScale(scale); + style.setImage(image); + _.extend(style, options); + return style; + }, + line: function (layer, options, feature, resolution) { + let linewidth = layer.get('lnWdth'); + let lineColor = layer.get('lnColor'); + let style = new Style({ + stroke: new Stroke({ + color: lineColor, + width: linewidth, + }), + text: new Text(), + }); + + if (GisApp.Module.toolbar && GisApp.Module.toolbar.defaults.blockDiagramVisible) { + let blockDiagramLayerId = GisApp.Module.toolbar.getBlockDiagramLayerId(layer.get('lyrId')); + if (blockDiagramLayerId) { + style = this.getArrowStyle(feature, lineColor, linewidth); + } + } + return style; + }, + poligon: function (layer, options, feature, resolution) { + const style = new Style(); + const color = layer.get('polgnColor'); + const fill = new Fill({ + color, + }); + style.setFill(fill); + _.extend(style, options); + return style; + }, + lineLabelPoligon: function (layer, options, feature, resolution) { + let style = new Style(); + const polgnColor = layer.get('polgnColor'); + let fill = new Fill({ + color: polgnColor, + }); + let lnWdth = layer.get('lnWdth'); + let lineColor = layer.get('lnColor'); + let lineDash = layer.get('lineDash'); + let stroke = new Stroke({ + color: lineColor || '#000000', + width: lnWdth, + lineDash: lineDash ? [10, 5] : null, + }); + let text = this.getText(layer, feature, resolution); + if (text) { + style.setText(text); + } + const overlayHtml = feature.get('html'); + if (overlayHtml) { + this.setOverlay(layer, feature, overlayHtml); + } + style.setFill(fill); + style.setStroke(stroke); + _.extend(style, options); + return style; + }, + imagePoligon: function (layer, options, feature, resolution) { + let style = new Style(); + let symbol = layer.get('iconNm'); + let retGeom = feature.getGeometry().getInteriorPoint(); + let image = new Icon({ + crossOrigin: 'anonymous', + imgSize: [20, 20], + src: this.layericonPath + symbol, + }); + + let scale = 0.5 / resolution > 1 ? 1 : 0.5 / resolution; + image.setScale(1); + style.setGeometry(retGeom); + style.setImage(image); + + _.extend(style, options); + return style; + }, + setOverlay: function (layer, feature, overlayHtml) { + const layerFeature = layer.getSource().getFeatures()[0]; + const layerCoordinate = layerFeature ? layerFeature.getGeometry().getCoordinates() : null; + + const properties = feature.getProperties(); + const overLayId = feature?.get('overLayId'); + const overLayMinZoom = layer.get('overLayMinZoom') || '12'; + const overLayOffset = layer?.get('overLayOffset'); + let findoOverlay = this.overlays[overLayId]; + if (findoOverlay) { + const zoom = GisApp.Module.core.map.getView().getZoom(); + if (Number(overLayMinZoom) < zoom) { + findoOverlay.options.element.style.display = 'block'; + } else { + findoOverlay.options.element.style.display = 'none'; + } + return findoOverlay; + } + let overlayContainer = document.createElement('div'); + document.body.appendChild(overlayContainer); + + if (typeof overlayHtml === 'string') { + //const sanitizedHtml = DOMPurify.sanitize(overlayHtml); + overlayContainer.innerHTML = sanitizedHtml; + } + // React 컴포넌트일 경우 createPortal() 사용 + else if (React.isValidElement(overlayHtml)) { + ReactDOM.createRoot(overlayContainer).render(createPortal(overlayHtml, overlayContainer)); + } + + // 오버레이 생성 + let overlay = new Overlay({ + element: overlayContainer, + positioning: 'bottom-center', + stopEvent: false, + offset: overLayOffset ? overLayOffset : [0, 0], // 오프셋 설정 (위치 조정) + }); + this.overlays[overLayId] = overlay; + + // feature의 좌표 가져오기 + let geometry = feature.getGeometry(); + let coordinate; + // Point인 경우 + if (geometry.getType() === 'Point') { + coordinate = geometry.getCoordinates(); + } + // LineString 또는 MultiLineString인 경우 + else if (geometry.getType() === 'LineString' || geometry.getType() === 'MultiLineString') { + coordinate = geometry.getFlatMidpoint(); // 선의 중간 좌표를 가져옴 + } + // Polygon 또는 MultiPolygon인 경우 + else if (geometry.getType() === 'Polygon' || geometry.getType() === 'MultiPolygon') { + coordinate = geometry.getExtent(); + const center = [(coordinate[0] + coordinate[2]) / 2, (coordinate[1] + coordinate[3]) / 2]; + coordinate = center; + } else { + console.error('Unsupported geometry type: ' + geometry.getType()); + return; + } + overlay.setPosition(coordinate); + GisApp.Module.core.map.addOverlay(overlay); + const zoom = GisApp.Module.core.map.getView().getZoom(); + if (Number(overLayMinZoom) < zoom) { + overlay.options.element.style.display = 'block'; + } else { + overlay.options.element.style.display = 'none'; + } + }, + label: function (layer, options, feature, resolution) { + let textBaseline = layer.get('textBaseline') || 'bottom'; + let textAlign = layer.get('textAlign') || 'center'; + let fontExtnColor = layer.get('fontExtnColor'); + let fontExtnWdth = Number(layer.get('fontExtnWdth') || '3.5'); + let fontIntnColor = layer.get('fontIntnColor'); + let style = new Style(); + let prop = feature.getProperties(); + let label = prop[layer.get('lblAtrbNm')]; + + let text = new Text({ + stroke: new Stroke({ + width: fontExtnWdth, + color: fontExtnColor, + }), + fill: new Fill({ + color: fontIntnColor, + }), + font: layer.get('font'), + textBaseline: textBaseline, + textAlign: textAlign, + text: label, + }); + style.setStroke(null); + style.setFill(null); + style.setText(text); + _.extend(style, options); + + return style; + }, + getText: function (layer, feature, resolution) { + let textBaseline = layer.get('textBaseline') || 'middle'; + let textAlign = layer.get('textAlign') || 'center'; + let fontExtnColor = layer.get('fontExtnColor'); + let fontExtnWdth = Number(layer.get('fontExtnWdth') || '1'); + let fontIntnColor = layer.get('fontIntnColor'); + let minViewSize = layer.get('minViewSize'); + if (feature == null) { + return; + } + let prop = feature.getProperties(); + let label = prop[layer.get('lblAtrbNm')]; + let scaleFactor = 1 / resolution; // 해상도가 낮아질수록 글자 크기 증가 + const zoom = GisApp.Module.core.map.getView().getZoom(); + if (zoom < minViewSize - 3) { + label = ''; + } + let text = new Text({ + stroke: new Stroke({ + color: '#FFFFFF', // 흰색 테두리 + width: fontExtnWdth, + }), + fill: new Fill({ + color: fontIntnColor, + width: fontExtnWdth, + }), + font: `${layer.get('fontNm')}`, // 동적으로 폰트 크기 적용 + textBaseline: textBaseline, + textAlign: textAlign, + overflow: true, + text: label, + offsetY: 20, + }); + + return text; + }, + getArrowStyle: function (feature, lineColor, linewidth) { + let styles = [ + new Style({ + stroke: new Stroke({ + color: lineColor, + width: linewidth, + }), + }), + ]; + let geomLienString = GisApp.Module.core.getGeomLineOfFeature(feature); + if (geomLienString) { + let startCoord = geomLienString.getFirstCoordinate(); + let endCoord = geomLienString.getLastCoordinate(); + + let dx = endCoord[0] - startCoord[0]; + let dy = endCoord[1] - startCoord[1]; + let rotation = Math.atan2(dy, dx); + + let centerCoord = GisApp.Module.core.getCenterOfLineFeature(feature); + styles.push( + new Style({ + geometry: new Point(centerCoord), + image: new RegularShape({ + fill: new Fill({ color: lineColor }), + points: 3, + radius: 6, + rotation: -rotation, + angle: Math.PI / 2, + }), + }) + ); + } + return styles; + }, + //레이어 스타일 반환 + getLayerStyle: function (layer, options) { + let gobjTy = layer.get('gobjTy'); + if (!gobjTy) return; + let style = this[gobjTy](layer, options); + return style; + }, + getLayerStyleFunction: function (layer, options) { + let gobjTy = layer.get('gobjTy'); + let styleFunction = function (feature, resolution) { + return this[gobjTy](layer, options, feature, resolution); + }.bind(this); + return styleFunction; + }, + adjustOverlayPosition: function (overlay, coordinate) { + const overlays = this.overlays; + let adjustedCoordinate = [...coordinate]; // 초기 좌표 설정 + const minDistance = 100000; // 최소 거리 (필요시 조정) + let offset = overlay.getOffset() || [0, 0]; + + let closestOverlay = null; + let closestDistance = Infinity; + + // 모든 오버레이의 위치를 비교하여 가장 가까운 오버레이 찾기 + Object.values(overlays).forEach(existingOverlay => { + if (existingOverlay) { + const existingCoordinate = existingOverlay.getPosition(); + const existingOffset = existingOverlay.getOffset() || [0, 0]; + + // 현재 오버레이의 좌표 가져오기 + const overlayCoordinate = overlay.getPosition(); + if (!overlayCoordinate || !existingCoordinate) return; + + // 거리 계산 (유클리드 거리) + const distance = Math.sqrt(Math.pow(overlayCoordinate[0] - existingCoordinate[0], 2) + Math.pow(overlayCoordinate[1] - existingCoordinate[1], 2)); + + // 가장 가까운 오버레이 업데이트 + if (distance > 0 && distance < closestDistance) { + closestDistance = distance; + closestOverlay = existingOverlay; + } else { + offset = [50, 50]; + } + } + }); + + // 가장 가까운 오버레이가 있으면 offset 조정 + if (closestOverlay && closestDistance < minDistance) { + const closestOffset = closestOverlay.getOffset() || [0, 0]; + // X, Y 좌표를 기존 offset 값의 반대로 설정 + offset = [-closestOffset[0], -closestOffset[1]]; + } + + // 최종 offset 적용 + overlay.setOffset(offset); + }, +}; + +export default GisOlStyle; diff --git a/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-boon.png b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-boon.png new file mode 100644 index 000000000..f65ce99f2 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-boon.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-hab.png b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-hab.png new file mode 100644 index 000000000..2ef70877e Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon-hab.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon.png b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon.png new file mode 100644 index 000000000..950edf246 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/marker/marker-icon.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area.png new file mode 100644 index 000000000..044562b55 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area_on.png new file mode 100644 index 000000000..0129ff1bd Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_area_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next.png new file mode 100644 index 000000000..075111dcf Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next_on.png new file mode 100644 index 000000000..4fdbe3a00 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_next_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev.png new file mode 100644 index 000000000..7e3fae7d2 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev_on.png new file mode 100644 index 000000000..fb2d55cfd Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_prev_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print.png new file mode 100644 index 000000000..82596b83d Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print_on.png new file mode 100644 index 000000000..bb7f27334 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_print_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler.png new file mode 100644 index 000000000..501e460a4 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler_on.png new file mode 100644 index 000000000..676a81bcc Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_ruler_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save.png new file mode 100644 index 000000000..c057b9377 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save_on.png new file mode 100644 index 000000000..bf8e6d623 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_save_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch.png new file mode 100644 index 000000000..38a5f507f Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch_on.png new file mode 100644 index 000000000..79fb54e03 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_sketch_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin.png new file mode 100644 index 000000000..ac32fbab0 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin_on.png new file mode 100644 index 000000000..f258a965f Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomin_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout.png new file mode 100644 index 000000000..2ee0b5161 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout_on.png new file mode 100644 index 000000000..58dcbfdae Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_cont_zoomout_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add.png new file mode 100644 index 000000000..fe4f0993f Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add_on.png new file mode 100644 index 000000000..37a836298 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_add_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del.png new file mode 100644 index 000000000..3bb372ab9 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del_on.png new file mode 100644 index 000000000..89e558739 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_del_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next.png new file mode 100644 index 000000000..df4913c90 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next_on.png new file mode 100644 index 000000000..4e5540b55 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_next_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev.png new file mode 100644 index 000000000..e94bf6d42 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev_on.png new file mode 100644 index 000000000..8645dedfa Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_prev_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save.png new file mode 100644 index 000000000..5f01dd55c Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save_on.png new file mode 100644 index 000000000..b035687d6 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_save_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt.png new file mode 100644 index 000000000..aaf2bbd08 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt_on.png new file mode 100644 index 000000000..02d035a74 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_slt_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap.png new file mode 100644 index 000000000..fc91d4304 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap_on.png new file mode 100644 index 000000000..f856d8fbe Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/btn_edit_snap_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/ico_gis_sprite.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/ico_gis_sprite.png new file mode 100644 index 000000000..3567cc6e4 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/ico_gis_sprite.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/icon-viewclose.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/icon-viewclose.png new file mode 100644 index 000000000..d93ce9c05 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/icon-viewclose.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map1.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map1.png new file mode 100644 index 000000000..7f96fcf5e Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map1.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map2.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map2.png new file mode 100644 index 000000000..1a93f50e8 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map2.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map3.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map3.png new file mode 100644 index 000000000..df80af6dc Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map3.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map4.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map4.png new file mode 100644 index 000000000..02d015d12 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/img_map4.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/loading.gif b/packages/common/business_components/src/ui/map/lib/images/toolbar/loading.gif new file mode 100644 index 000000000..7c3683bb4 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/loading.gif differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/map_pin.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/map_pin.png new file mode 100644 index 000000000..b6cc2f6aa Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/map_pin.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_off.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_off.png new file mode 100644 index 000000000..d5dea9f67 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_off.png differ diff --git a/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_on.png b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_on.png new file mode 100644 index 000000000..9cb573875 Binary files /dev/null and b/packages/common/business_components/src/ui/map/lib/images/toolbar/toggle_on.png differ diff --git a/packages/common/business_components/src/ui/map/lib/util.js b/packages/common/business_components/src/ui/map/lib/util.js new file mode 100644 index 000000000..9fe55869b --- /dev/null +++ b/packages/common/business_components/src/ui/map/lib/util.js @@ -0,0 +1,596 @@ + +//공통 +export const util = { + getOneToArrayByObjectKey: function (arr, key, value) { + let result = {}; + arr.forEach(item => { + if (item[key] === value) { + result = item; + } + }); + return result; + }, + groupByProperty: (arr, prop, parentProp, group) => { + const tree = []; + const mappedArr = {}; + let arrElem; + let mappedElem; + // 먼저 배열의 노드를 개체에 매핑하고 -> 해시 테이블을 생성 + for (let i = 0; i < arr.length; i++) { + arrElem = arr[i]; + if (group && arrElem.mnuGroup !== group) continue; + mappedArr[arrElem[prop]] = arrElem; + mappedArr[arrElem[prop]].children = []; + } + + for (const mnuCd in mappedArr) { + if (mappedArr.hasOwnProperty(mnuCd)) { + mappedElem = mappedArr[mnuCd]; + // 요소가 루트 수준에 없으면 자식의 부모 배열에 추가. + if (mappedElem[parentProp] && mappedArr[mappedElem[parentProp]]?.children) { + mappedArr[mappedElem[parentProp]].children.push(mappedElem); + } + // 요소가 루트 수준에 있는 경우 첫 번째 수준 요소 배열에 추가 + else { + tree.push(mappedElem); + } + } + } + return tree; + }, + getLowestChildren: function (obj) { + if (!obj.children || obj.children.length === 0) { + return obj; + } else { + return this.getLowestChildren(obj.children[0]); + } + }, + hasClass: (e, clazz) => { + return e.target.classList.contains(clazz); + }, + isNull: function (str) { + if (typeof str === 'undefined' || str === null || str === 'null' || str === '' || str === 'undefined') { + return true; + } + return false; + }, + nvl: function (str, returnVal) { + if (this.isNull(str)) { + return returnVal; + } + return str; + }, + isNumeric: function (str) { + const pattern = /\d/; + return pattern.test(str); + }, + isAlpha: function (str) { + const pattern = /[a-zA-Z]/; + return pattern.test(str); + }, + isAlphaNumeric: function (str) { + const pattern = /^[a-zA-Z0-9]+$/; + return pattern.test(str); + }, + isSpecialChar: function (str) { + const pattern = /[~!@#$%^&*()_+|<>?:{}]/; + return pattern.test(str); + }, + isKoreanAplhaNumeric: function (str) { + const pattern = /^[ㄱ-ㅎ|ㅏ-ㅣ|가-힣|a-zA-Z0-9]+$/; + return pattern.test(str); + }, + isValidPassword: function (str) { + const pattern = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*(),.?":{}|<>])[a-zA-Z0-9!@#$%^&*(),.?":{}|<>]{8,20}$/; + return pattern.test(str); + }, + isValidExt: function (fileName, allowedExtensions) { + const fileExtension = fileName.split('.').pop().toLowerCase(); + return allowedExtensions.includes(fileExtension); + }, + isValidAllDate: function (dateString) { + const formats = ['YYYY', 'YYYYMM', 'YYYYMMDD']; + for (let format of formats) { + if (moment(dateString, format, true).isValid()) { + return true; + } + } + return false; + }, + isEmptyObject: function (param) { + return Object.keys(param).length === 0 && param.constructor === Object; + }, + castArrayObjectToList: function (array, key) { + var newArray = []; + for (var i = 0; i < array.length; i++) { + newArray.push(array[i][key]); + } + return newArray; + }, + arraysMatch: function (arr1, arr2) { + // 길이가 같은지 확인 + if (arr1.length !== arr2.length) return false; + + // 각 요소가 일치하는지 확인 + return arr1.every((element, index) => element === arr2[index]); + }, + //상위뎁스 객체들 조회 + findObjectByKeyAndValue: function ({ array, find: { key: findKey, value: findValue }, result: { key: resultKey }, parents = [] }) { + for (let i = 0; i < array.length; i++) { + let obj = array[i]; + let siblings = [...array]; + //siblings.splice(i, 1); // 현재 메뉴를 제거하여 형제 메뉴들만 남김 + let currentParents = [...parents, { [resultKey]: obj, siblings: siblings }]; + + if (obj[findKey] === findValue) { + let formattedResult = currentParents.reduce((acc, cur, idx) => { + acc[`${resultKey}${idx + 1}`] = { active: cur[resultKey], list: cur.siblings }; + return acc; + }, {}); + return formattedResult; + } + if (obj.children) { + let result = this.findObjectByKeyAndValue({ array: obj.children, find: { key: findKey, value: findValue }, result: { key: resultKey }, parents: currentParents }); + if (result !== null) { + return result; + } + } + } + return null; + }, + autoComma(val, returnVal) { + if (!val && val !== 0) return returnVal ? returnVal : ''; + + // 문자열로 변환하고, 숫자와 소수점만을 허용 + val = String(val).replace(/[^0-9.-]/g, ''); + + // 소수점을 기준으로 숫자를 분리 + let [integerPart, decimalPart] = val.split('.'); + + // 정수 부분에 대해 쉼표 추가 + integerPart = integerPart.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); + + // 소수점 이하 숫자가 있다면 다시 합치기 + return decimalPart ? `${integerPart}.${decimalPart}` : integerPart; + }, + removeComma: function (str) { + str = String(str); + return str.replace(/[^\d]+/g, ''); + }, + removeSpaceAndTrim: function (str) { + if (str) { + str = String(str); + return str.replace(/\s/g, ''); + } else { + return str; + } + }, + isNotWhiteSpace: function (str) { + const hasWhitespace = /\s/.test(str); + return !hasWhitespace; + }, + //숫자만 남기고 모두 제거해서 반환 + retainOnlyNumbers: function (str) { + str = String(str); + return str.replace(/\D+/g, ''); + }, + proxyToObject: function (proxy) { + return JSON.parse(JSON.stringify(Object.assign({}, proxy))); + }, + dateFormatter: function ({ input, isHan = false }) { + const value = this.retainOnlyNumbers(input); + + if (value.length === 6) { + const date = moment(value, 'YYYYMM'); + return date.format(isHan ? 'YYYY년 MM월' : 'YYYY-MM'); + } else if (value.length === 8) { + const date = moment(value, 'YYYYMMDD'); + return date.format(isHan ? 'YYYY년 MM월 DD일' : 'YYYY-MM-DD'); + } else if (value.length === 14) { + const date = moment(value, 'YYYYMMDDHHmmss'); + return date.format(isHan ? 'YYYY년 MM월 DD일 HH시 mm분 ss초' : 'YYYY-MM-DD HH:MM:SS'); + } else { + return input; + } + }, + generateYearArray: function (num) { + const currentYear = moment().year(); + if (num <= 1 || isNaN(num)) { + return [(currentYear - 2).toString(), (currentYear - 1).toString(), currentYear.toString()]; + } + + const years = []; + for (let i = num - 1; i >= 0; i--) { + years.push((currentYear - i).toString()); + } + + return years; + }, + getFileId: function () { + let timeStamp = moment().format('YYYYMMDDHHmmssSSS'); // YYYYMMDDHHmmssSSS 형태로 타임스탬프를 생성 + return 'CFM_' + timeStamp.slice(-12); // slice(-12)는 문자열의 끝에서 12자리를 추출합니다. + }, + lpad: function (number, pad, length) { + let str = number.toString(); + while (str.length < length) { + str = pad + str; + } + return str; + }, + convertKeysToCamelCase: function (obj) { + if (obj === null || typeof obj !== 'object') { + return obj; + } + return Object.keys(obj).reduce((acc, key) => { + const camelCaseKey = key.replace(/([-_]\w)/g, match => match.replace(/[-_]/, '').toUpperCase()); + acc[camelCaseKey] = typeof obj[key] === 'object' ? this.convertKeysToCamelCase(obj[key]) : obj[key]; + return acc; + }, {}); + }, + blobUrlToBlob: async blobUrl => { + try { + const response = await fetch(blobUrl); + return await response.blob(); + } catch (error) { + console.error(error); + return new Blob(); + } + }, + hexToRgba: hex => { + const r = parseInt(hex.substring(1, 3), 16); + const g = parseInt(hex.substring(3, 5), 16); + const b = parseInt(hex.substring(5, 7), 16); + return `rgba(${r}, ${g}, ${b} , 0.5)`; + }, + rgbaToHex: rgba => { + /*rgba값을 가져와서 rgb 로 추출 후 hex 값 반환 */ + if (!rgba) return false; + const rgbArray = rgba.match(/\d+/g).slice(0, 3).map(Number); + return `#${((1 << 24) | (rgbArray[0] << 16) | (rgbArray[1] << 8) | rgbArray[2]).toString(16).slice(1).toUpperCase()}`; + }, + rgbAddOpacity: (rgb, opacity) => { + const rgbValues = rgb.match(/\d+/g); // rgb 값을 배열로 추출 + const colorWithOpacity = `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`; // rgba로 변환 + return colorWithOpacity; + }, + generateMonthArray: (pSmonth, pEmonth) => { + let startYear = Math.floor(pSmonth / 100); + let startMonth = pSmonth % 100; + let endYear = Math.floor(pEmonth / 100); + let endMonth = pEmonth % 100; + + let months = []; + + for (let year = startYear; year <= endYear; year++) { + let monthStart = year === startYear ? startMonth : 1; + let monthEnd = year === endYear ? endMonth : 12; + + for (let month = monthStart; month <= monthEnd; month++) { + let monthString = month < 10 ? `0${month}` : `${month}`; + months.push(`${year}${monthString}`); + } + } + + return months; + }, + findDuplicatesForArr: (arr, key) => { + const map = new Map(); + const duplicates = []; + + arr.forEach(item => { + const keyValue = item[key]; + if (map.has(keyValue)) { + if (!duplicates.includes(keyValue)) { + duplicates.push(keyValue); + } + } else { + map.set(keyValue, true); + } + }); + + return duplicates; + }, + createHtmlFromJson: (html, data) => { + if (!html || util.isEmptyObject(html)) return null; + const { classNm, headerKey, rows } = JSON.parse(html); + if (!rows) return null; + const tableRows = rows + .map(row => { + const value = data?.[row.key] ?? null; + let displayValue; + if (typeof value === 'number') { + displayValue = util.autoComma(value, 0); + } else if (typeof value === 'string') { + displayValue = util.nvl(value, '-'); + } else { + displayValue = '-'; + } + return ` + + ${row.th} + ${displayValue} + + `; + }) + .join(''); + return ` +
+ + + + + ${tableRows} +
+ ${data?.[headerKey] ?? '-'} +
+
+ `; + }, +}; + +/** + * + * 다이렉트로 import하여 사용하기 위해 export로 수정중 + * + * */ + +//객체 배열에서 특정 key의 value인 것을 추출 +export const getArrayByObjectKey = function (arr, key, value) { + return arr.filter(item => item[key] === value); +}; + +//소수점 1자리를 기준으로 점을 언더스코어로 변경 +export const numberDotToUnderscore = function (num) { + if (Number.isInteger(num)) { + return num.toString(); + } else { + return num.toFixed(1).replace('.', '_'); + } +}; + +export const stripHTML = function (htmlString) { + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, 'text/html'); + return doc.body.textContent || ''; +}; + +//URL에서 매개변수 추출 +export function getParameterByName(name, url = window.location.search) { + name = name.replace(/[\[\]]/g, '\\$&'); + var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); +} + +export function dateFormatForDB({ date: isoString, format = 'YYYY-MM-DD HH:MI:SS' }) { + if (!isoString) return ''; + + let date = new Date(isoString); + + let year = date.getFullYear(); + let month = (date.getMonth() + 1).toString().padStart(2, '0'); + let day = date.getDate().toString().padStart(2, '0'); + + let hours = date.getHours().toString().padStart(2, '0'); + let minutes = date.getMinutes().toString().padStart(2, '0'); + let seconds = date.getSeconds().toString().padStart(2, '0'); + + return ( + format + // + .replace('YYYY', year) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('MI', minutes) + .replace('SS', seconds) + ); +} + +//blob 다운로드 +//blob 다운로드 +export function downloadFromBlob(res, data) { + const url = URL.createObjectURL(new Blob([res.data], { type: res.data.type })); + const link = document.createElement('a'); + + const contentDisposition = res.headers?.['content-disposition']; // 파일 이름 + let fileName = data?.fileName || 'export'; //기본 파일 이름 + + if (contentDisposition) { + const [fileNameMatch] = contentDisposition.split(';').filter(str => str.includes('filename')); + if (fileNameMatch) { + [, fileName] = fileNameMatch.split('='); + // 파일명이 "UTF-8''" 등으로 인코딩되어 있을 수 있으니 decodeURIComponent 추가 + fileName = decodeURIComponent(fileName); + } + } + + link.href = url; + // 브라우저 인코딩 관련 이슈 방지를 위해 파일명에 대한 추가 decodeURI 처리 + link.download = decodeURIComponent(fileName).replace(/"/g, '').trim(); + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + + link.remove(); + URL.revokeObjectURL(url); +} + +export function downloadBlobAsFile(blob, filename = 'export') { + // 브라우저에 따라 인코딩 처리 햔재는 파이어폭스만 + const url = URL.createObjectURL(blob); + const encodedFilename = encodeURIComponent(filename); + const downloadLink = document.createElement('a'); + + downloadLink.href = url; + downloadLink.download = decodeURIComponent(encodedFilename); + + document.body.appendChild(downloadLink); + downloadLink.click(); + + document.body.removeChild(downloadLink); + URL.revokeObjectURL(url); +} + +export function viewFile(res, data) { + // Blob URL 생성 + const url = URL.createObjectURL(new Blob([res.data], { type: res.data.type })); + + let fileType = null; + let fileName = data?.fileName || 'export'; // 기본 파일 이름 + + // PDF 및 이미지 타입의 MIME 타입 확인 + if (res.data.type === 'application/pdf') { + fileType = 'pdf'; + } else if (res.data.type.startsWith('image/')) { + fileType = 'img'; + } else if (res.data.type === 'application/octet-stream') { + // Content-Disposition 헤더에서 파일 이름 추출 + const contentDisposition = res.headers?.['content-disposition']; + if (contentDisposition) { + const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); + if (fileNameMatch && fileNameMatch[1]) { + fileName = fileNameMatch[1].replace(/['"]/g, ''); // 파일 이름 추출 + fileName = decodeURIComponent(fileName); // URL 인코딩된 파일 이름을 디코딩 + } + } + + // 파일 확장자 추출 + const fileExtension = fileName.split('.').pop().toLowerCase(); + + // 확장자를 통해 파일 타입 결정 + if (fileExtension === 'pdf') { + fileType = 'pdf'; + } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(fileExtension)) { + fileType = 'img'; + } else { + console.error('지원하지 않는 파일 형식입니다.'); + } + } + + // URL과 파일 타입 반환 + return { url, fileType, fileName }; +} + +export function getToday() { + return getDate('today'); +} + +export function getDate(type, diff) { + const date = new Date(); + + switch (type) { + case 'year': + date.setFullYear(date.getFullYear() + diff); + break; + case 'month': + date.setMonth(date.getMonth() + diff); + break; + case 'day': + date.setDate(date.getDate() + diff); + break; + case 'today': + date.setDate(date.getDate()); + break; + default: + console.log('잘못된 타입입니다. 올바른 타입: 년, 월, 일'); + return; + } + + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + + return `${year}${month}${day}`; +} + +//promise 처리 +export const byPromise = function (actions) { + return actions.reduce((promise, action) => { + return promise.then(async () => { + if (typeof action === 'function') { + return await action(); + } else { + return await action; + } + }); + }, Promise.resolve()); +}; + +export const getMessage = (msgGroup, msgKey, innerText, fix) => { + let resultString = GLOBAL.MESSAGE[msgGroup][msgKey]; + const prefix = fix?.prefix || ''; + const suffix = fix?.suffix || ''; + + if (innerText) { + for (const [key, value] of Object.entries(innerText)) { + const regex = new RegExp(`\\$\{${key}\}`, 'g'); + resultString = resultString.replace(regex, value); + } + } + + if (resultString) resultString = resultString.replace(/\\n/g, '\n'); + return resultString ? `${prefix}${resultString}${suffix}` : '안내 메시지 로딩 오류 - 관리자에게 문의하세요.'; +}; + +// global.js의 message를 읽어오는 alert +export const gAlert = (msgGroup, msgKey, innerText, fix) => { + alert(getMessage(msgGroup, msgKey, innerText, fix)); +}; + +// message를 confirm +export const gConfirm = (msgGroup, msgKey, innerText, fix) => { + return window.confirm(getMessage(msgGroup, msgKey, innerText, fix)); +}; + +// message를 함수 리턴 +export const gMessage = getMessage; + +// message를 save후 응답까지 처리 +export const gSaveCommonConfirm = async ({ message, api = async () => {}, param = {}, success, fail, prefix }) => { + try { + if (!gConfirm('CONFIRM', `${message}_QUEST`, null, { prefix })) return; + + const result = await api(param, { isAlert: false }); + + if (result?.code !== 200) return; + + const rep = message === 'UPLOAD' ? result.data.data : result.data; + + if (rep > 0) { + gAlert('INFO', `${message}_COMPLETE`); + await success?.(); + } else { + gAlert('ERROR', `${message}_FAIL`); + await fail?.(); + } + } catch (error) { + console.error('오류 발생:', error); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력 + } +}; + +export const nullToValue = (text, { value = '-', prefix = '', suffix = '' } = {}) => { + if (!text || text === value) { + return value; + } + return `${prefix}${text}${suffix}`; +}; + +export const filterArrayOfObjectsByKeys = (arrayOfObjects, keys) => { + return arrayOfObjects.map(obj => { + return keys.reduce((filteredObj, key) => { + if (obj.hasOwnProperty(key)) { + filteredObj[key] = obj[key]; + } + return filteredObj; + }, {}); + }); +}; + +export const isMobile = () => { + const width = window.innerWidth > 0 ? window.innerWidth : window.screen.width; + + return width < GLOBAL.MOBILE_SIZE; +};