Merge branch 'dev' into feature/test-jy
# Conflicts: # src/components/fabric/QPolygon.js
This commit is contained in:
commit
11e84ed614
4320
package-lock.json
generated
4320
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,10 +24,12 @@
|
|||||||
"react-datepicker": "^7.3.0",
|
"react-datepicker": "^7.3.0",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-responsive-modal": "^6.4.2",
|
"react-responsive-modal": "^6.4.2",
|
||||||
|
"react-toastify": "^10.0.5",
|
||||||
"recoil": "^0.7.7",
|
"recoil": "^0.7.7",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@turf/turf": "^7.0.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"prisma": "^5.18.0",
|
"prisma": "^5.18.0",
|
||||||
|
|||||||
@ -1,16 +1,26 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
|
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from '@nextui-org/react'
|
||||||
|
|
||||||
|
import { AxiosType, useAxios } from '@/hooks/useAxios'
|
||||||
|
// import { get } from '@/lib/Axios'
|
||||||
|
|
||||||
import QSelect from '@/components/ui/QSelect'
|
import QSelect from '@/components/ui/QSelect'
|
||||||
|
|
||||||
import styles from './changelog.module.css'
|
import styles from './changelog.module.css'
|
||||||
import { get } from '@/lib/Axios'
|
|
||||||
|
|
||||||
export default function changelogPage() {
|
export default function changelogPage() {
|
||||||
|
const { get } = useAxios()
|
||||||
const testVar = process.env.NEXT_PUBLIC_TEST
|
const testVar = process.env.NEXT_PUBLIC_TEST
|
||||||
|
|
||||||
const handleUsers = async () => {
|
const handleUsers = async () => {
|
||||||
const users = await get('/api/user/find-all')
|
// const users = await get('/api/user/find-all')
|
||||||
console.log(users)
|
const params = {
|
||||||
|
type: AxiosType.INTERNAL,
|
||||||
|
url: '/api/user/find-all',
|
||||||
|
}
|
||||||
|
const users = await get(params)
|
||||||
|
console.log('users', users)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
import './globals.css'
|
|
||||||
import '../styles/style.scss'
|
|
||||||
import Headers from '@/components/Headers'
|
|
||||||
import RecoilRootWrapper from './RecoilWrapper'
|
import RecoilRootWrapper from './RecoilWrapper'
|
||||||
import UIProvider from './UIProvider'
|
import UIProvider from './UIProvider'
|
||||||
import { headers } from 'next/headers'
|
import { headers } from 'next/headers'
|
||||||
|
import Headers from '@/components/Headers'
|
||||||
|
|
||||||
|
import { ToastContainer } from 'react-toastify'
|
||||||
|
import QModal from '@/components/common/modal/QModal'
|
||||||
|
|
||||||
|
import './globals.css'
|
||||||
|
import '../styles/style.scss'
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] })
|
const inter = Inter({ subsets: ['latin'] })
|
||||||
|
|
||||||
@ -24,6 +29,8 @@ export default function RootLayout({ children }) {
|
|||||||
{headerPathname !== '/login' && <Headers />}
|
{headerPathname !== '/login' && <Headers />}
|
||||||
<RecoilRootWrapper>
|
<RecoilRootWrapper>
|
||||||
<UIProvider>{children}</UIProvider>
|
<UIProvider>{children}</UIProvider>
|
||||||
|
<QModal />
|
||||||
|
<ToastContainer />
|
||||||
</RecoilRootWrapper>
|
</RecoilRootWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -4,15 +4,22 @@ import { useEffect, useMemo, useState } from 'react'
|
|||||||
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { useRecoilState } from 'recoil'
|
||||||
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
|
|
||||||
|
import { AxiosType, useAxios } from '@/hooks/useAxios'
|
||||||
|
|
||||||
import { Button } from '@nextui-org/react'
|
import { Button } from '@nextui-org/react'
|
||||||
|
|
||||||
import SingleDatePicker from './common/datepicker/SingleDatePicker'
|
import SingleDatePicker from './common/datepicker/SingleDatePicker'
|
||||||
import RangeDatePicker from './common/datepicker/RangeDatePicker'
|
import RangeDatePicker from './common/datepicker/RangeDatePicker'
|
||||||
import QGrid from './common/grid/QGrid'
|
import QGrid from './common/grid/QGrid'
|
||||||
import QModal from './common/modal/QModal'
|
import { QToast } from '@/hooks/useToast'
|
||||||
|
|
||||||
export default function Intro() {
|
export default function Intro() {
|
||||||
const [open, setOpen] = useState(false)
|
const { get } = useAxios()
|
||||||
|
|
||||||
|
// const [open, setOpen] = useState(false)
|
||||||
const [startDate, setStartDate] = useState(new Date())
|
const [startDate, setStartDate] = useState(new Date())
|
||||||
const singleDatePickerProps = {
|
const singleDatePickerProps = {
|
||||||
startDate,
|
startDate,
|
||||||
@ -35,6 +42,9 @@ export default function Intro() {
|
|||||||
isPageable: false,
|
isPageable: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
|
const [contents, setContent] = useRecoilState(modalContent)
|
||||||
|
|
||||||
const modelProps = {
|
const modelProps = {
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
@ -60,8 +70,9 @@ export default function Intro() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
// const response = await fetch('https://www.ag-grid.com/example-assets/space-mission-data.json')
|
||||||
const data = await response.json()
|
// const data = await response.json()
|
||||||
|
const data = await get({ type: AxiosType.EXTERNAL, url: 'https://www.ag-grid.com/example-assets/space-mission-data.json' })
|
||||||
setGridProps({ ...gridProps, gridData: data })
|
setGridProps({ ...gridProps, gridData: data })
|
||||||
}
|
}
|
||||||
fetchData()
|
fetchData()
|
||||||
@ -95,10 +106,35 @@ export default function Intro() {
|
|||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<div className="text-2xl">QModal</div>
|
<div className="text-2xl">QModal</div>
|
||||||
<div>
|
<div>
|
||||||
<Button color="primary" onClick={() => setOpen(true)}>
|
{/* <Button color="primary" onClick={() => setOpen(true)}>
|
||||||
Open Modal
|
Open Modal
|
||||||
</Button>
|
</Button>
|
||||||
<QModal {...modelProps}>{ipsum}</QModal>
|
<QModal {...modelProps}>{ipsum}</QModal> */}
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setContent(ipsum)
|
||||||
|
setOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open Modal
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<div className="text-2xl">QToast</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
QToast({
|
||||||
|
message: 'This is a toast message',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open Toast
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -2,14 +2,15 @@ import { useCanvas } from '@/hooks/useCanvas'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Mode, useMode } from '@/hooks/useMode'
|
import { Mode, useMode } from '@/hooks/useMode'
|
||||||
import { Button } from '@nextui-org/react'
|
import { Button } from '@nextui-org/react'
|
||||||
|
|
||||||
import RangeSlider from './ui/RangeSlider'
|
import RangeSlider from './ui/RangeSlider'
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState } from '@/store/canvasAtom'
|
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray, templateTypeState, compassState } from '@/store/canvasAtom'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
||||||
import { calculateIntersection } from '@/util/canvas-util'
|
import { calculateIntersection } from '@/util/canvas-util'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
import { toGeoJSON } from '@/util/qpolygon-utils'
|
||||||
|
|
||||||
export default function Roof2() {
|
export default function Roof2() {
|
||||||
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
||||||
@ -35,6 +36,8 @@ export default function Roof2() {
|
|||||||
|
|
||||||
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
||||||
|
|
||||||
|
const [compass, setCompass] = useRecoilState(compassState)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mode,
|
mode,
|
||||||
setMode,
|
setMode,
|
||||||
@ -51,6 +54,7 @@ export default function Roof2() {
|
|||||||
makeRoofPatternPolygon,
|
makeRoofPatternPolygon,
|
||||||
createRoofRack,
|
createRoofRack,
|
||||||
drawRoofPolygon,
|
drawRoofPolygon,
|
||||||
|
drawCellInTrestle,
|
||||||
} = useMode()
|
} = useMode()
|
||||||
|
|
||||||
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
||||||
@ -492,6 +496,10 @@ export default function Roof2() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setCompassState = (degree) => {
|
||||||
|
setCompass(degree)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{canvas && (
|
{canvas && (
|
||||||
@ -521,12 +529,27 @@ export default function Roof2() {
|
|||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
||||||
지붕패턴2
|
지붕패턴2
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(90)}>
|
||||||
|
방위표◀
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(270)}>
|
||||||
|
방위표▶
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(180)}>
|
||||||
|
방위표▲
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setCompassState(0)}>
|
||||||
|
방위표▼
|
||||||
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.ROOF_TRESTLE)}>
|
||||||
지붕가대생성
|
지붕가대생성
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.FILL_CELLS)}>
|
||||||
태양광셀생성
|
태양광셀생성
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => setMode(Mode.CELL_POWERCON)}>
|
||||||
|
파워콘설치
|
||||||
|
</Button>
|
||||||
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
||||||
텍스트박스 모드
|
텍스트박스 모드
|
||||||
</Button>
|
</Button>
|
||||||
@ -542,9 +565,10 @@ export default function Roof2() {
|
|||||||
{/*<Button className="m-1 p-2" onClick={zoomIn}>
|
{/*<Button className="m-1 p-2" onClick={zoomIn}>
|
||||||
확대
|
확대
|
||||||
</Button>
|
</Button>
|
||||||
|
*/}
|
||||||
<Button className="m-1 p-2" onClick={zoomOut}>
|
<Button className="m-1 p-2" onClick={zoomOut}>
|
||||||
축소
|
축소
|
||||||
</Button>*/}
|
</Button>
|
||||||
현재 줌 : {zoom}%
|
현재 줌 : {zoom}%
|
||||||
<Button className="m-1 p-2" onClick={makeLine}>
|
<Button className="m-1 p-2" onClick={makeLine}>
|
||||||
선 추가
|
선 추가
|
||||||
@ -596,7 +620,10 @@ export default function Roof2() {
|
|||||||
지붕타입 지붕재
|
지붕타입 지붕재
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="m-1 p-2" onClick={createRoofRack}>
|
<Button className="m-1 p-2" onClick={createRoofRack}>
|
||||||
지붕가대
|
지붕가대설치
|
||||||
|
</Button>
|
||||||
|
<Button className="m-1 p-2" onClick={drawCellInTrestle}>
|
||||||
|
선택한 가대 셀채우기
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||||
|
|
||||||
import { Modal } from 'react-responsive-modal'
|
import { Modal } from 'react-responsive-modal'
|
||||||
|
|
||||||
|
import { modalContent, modalState } from '@/store/modalAtom'
|
||||||
|
|
||||||
import 'react-responsive-modal/styles.css'
|
import 'react-responsive-modal/styles.css'
|
||||||
|
|
||||||
export default function QModal(props) {
|
export default function QModal() {
|
||||||
const { open, setOpen, children } = props
|
const [open, setOpen] = useRecoilState(modalState)
|
||||||
|
const children = useRecoilValue(modalContent)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={() => setOpen(false)} center>
|
<Modal open={open} onClose={() => setOpen(false)} center>
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { fabric } from 'fabric'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util'
|
||||||
import { calculateAngle, drawHippedRoof } from '@/util/qpolygon-utils'
|
import { calculateAngle, drawHippedRoof, inPolygon, lineIntersect, splitPolygonWithLines, toGeoJSON } from '@/util/qpolygon-utils'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||||
type: 'QPolygon',
|
type: 'QPolygon',
|
||||||
@ -238,14 +239,12 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
|
|
||||||
const rectPoints = [
|
const rectPoints = [
|
||||||
{ x: rectLeft, y: rectTop },
|
{ x: rectLeft, y: rectTop },
|
||||||
{ x: rectLeft + rectWidth, y: rectTop },
|
|
||||||
{ x: rectLeft, y: rectTop + rectHeight },
|
{ x: rectLeft, y: rectTop + rectHeight },
|
||||||
{ x: rectLeft + rectWidth, y: rectTop + rectHeight },
|
{ x: rectLeft + rectWidth, y: rectTop + rectHeight },
|
||||||
|
{ x: rectLeft + rectWidth, y: rectTop },
|
||||||
]
|
]
|
||||||
|
|
||||||
const allPointsInside = rectPoints.every((point) => this.inPolygon(point))
|
if (inPolygon(this.points, rectPoints)) {
|
||||||
|
|
||||||
if (allPointsInside) {
|
|
||||||
const rect = new fabric.Rect({
|
const rect = new fabric.Rect({
|
||||||
left: rectLeft,
|
left: rectLeft,
|
||||||
top: rectTop,
|
top: rectTop,
|
||||||
@ -275,7 +274,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
this.cells = drawCellsArray
|
this.cells = drawCellsArray
|
||||||
return drawCellsArray
|
return drawCellsArray
|
||||||
},
|
},
|
||||||
fillCellABTemplate(cell = { width: 50, height: 100, padding: 5, parallelPoint: undefined, startX: 0, startY: 0 }) {
|
fillCellABType(cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1 }) {
|
||||||
const points = this.points
|
const points = this.points
|
||||||
|
|
||||||
const minX = Math.min(...points.map((p) => p.x)) //왼쪽
|
const minX = Math.min(...points.map((p) => p.x)) //왼쪽
|
||||||
@ -293,44 +292,43 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
const rows = Math.floor((boundingBoxHeight + cell.padding) / (rectHeight + cell.padding))
|
const rows = Math.floor((boundingBoxHeight + cell.padding) / (rectHeight + cell.padding))
|
||||||
|
|
||||||
//전체 높이에서 패딩을 포함하고 rows를 곱해서 여백길이를 계산 후에 2로 나누면 반높이를 넣어서 중간으로 정렬
|
//전체 높이에서 패딩을 포함하고 rows를 곱해서 여백길이를 계산 후에 2로 나누면 반높이를 넣어서 중간으로 정렬
|
||||||
const tmpHeight = (boundingBoxHeight - (rectHeight + cell.padding) * rows) / 2
|
|
||||||
//센터 정렬시에 쓴다 체크박스가 존재함 TODO: if문 추가해서 정렬해야함
|
let centerHeight = rows > 1 ? (boundingBoxHeight - (rectHeight + cell.padding / 2) * rows) / 2 : (boundingBoxHeight - rectHeight * rows) / 2 //rows 1개 이상이면 cell 을 반 나눠서 중간을 맞춘다
|
||||||
const tmpWidth = (boundingBoxWidth - (rectWidth + cell.padding) * cols) / 2
|
let centerWidth = cols > 1 ? (boundingBoxWidth - (rectWidth + cell.padding / 2) * cols) / 2 : (boundingBoxWidth - rectWidth * cols) / 2
|
||||||
|
|
||||||
const drawCellsArray = [] //그려진 셀의 배열
|
const drawCellsArray = [] //그려진 셀의 배열
|
||||||
|
|
||||||
let idx = 1
|
let idx = 1
|
||||||
|
|
||||||
if (cell.parallelPoint > -1) {
|
|
||||||
//4각형 이상이면 각 꼭지점에서 위, 아래로 이동하면서 채움
|
|
||||||
//앞에서 -1로 선언함
|
|
||||||
if (cell.parallelPoint === 1 || cell.parallelPoint === 2) {
|
|
||||||
//ㄴ자 역ㄱ자
|
|
||||||
//밑에서 위로 올라가야하면
|
|
||||||
cell.startY = cell.startY - (rectHeight + cell.padding) * rows
|
|
||||||
if (cell.parallelPoint === 2) {
|
|
||||||
cell.startX = cell.startX - (rectWidth + cell.padding) * cols
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (cell.parallelPoint === 5) {
|
|
||||||
cell.startX = cell.startX - (rectWidth + cell.padding) * cols
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let startXPos, startYPos
|
let startXPos, startYPos
|
||||||
|
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
for (let j = 0; j < rows; j++) {
|
for (let j = 0; j < rows; j++) {
|
||||||
if (cell.parallelPoint > -1) {
|
const rectPoints = []
|
||||||
startXPos = cell.startX + i * (rectWidth + cell.padding)
|
|
||||||
startYPos = cell.startY + j * (rectHeight + cell.padding)
|
if (cell.referenceDirection !== 'none') {
|
||||||
|
//4각형은 기준점이 없다
|
||||||
|
|
||||||
|
if (cell.referenceDirection === 'top') {
|
||||||
|
//top, bottom은 A패턴만
|
||||||
|
if (cell.wallDirection === 'left') {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = minY + j * rectHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
startXPos = minX + i * (rectWidth + cell.padding)
|
startXPos = maxX - (1 + i) * rectWidth - 0.01
|
||||||
startYPos = minY + j * (rectHeight + cell.padding)
|
startYPos = minY + j * rectHeight + 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rectPoints = []
|
if (j > 0) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
rectPoints.push(
|
rectPoints.push(
|
||||||
{ x: startXPos, y: startYPos },
|
{ x: startXPos, y: startYPos },
|
||||||
@ -338,13 +336,182 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
{ x: startXPos, y: startYPos + rectHeight },
|
{ x: startXPos, y: startYPos + rectHeight },
|
||||||
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||||
)
|
)
|
||||||
|
} else if (cell.referenceDirection === 'bottom') {
|
||||||
|
if (cell.wallDirection === 'left') {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
const allPointsInside = rectPoints.every((point) => this.inPolygon(point))
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startXPos = maxX - i * rectWidth - 0.01
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
startXPos = startXPos - rectWidth //우 -> 좌 들어가야해서 마이너스 처리
|
||||||
|
}
|
||||||
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.referenceDirection === 'left') {
|
||||||
|
//여기서부턴 B패턴임
|
||||||
|
if (cell.wallDirection === 'top') {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = minY + j * rectHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding //밑으로
|
||||||
|
}
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos + j * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos + rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startXPos = minX + i * rectWidth
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
|
||||||
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||||
|
}
|
||||||
|
} else if (cell.referenceDirection === 'right') {
|
||||||
|
if (cell.wallDirection === 'top') {
|
||||||
|
startXPos = maxX - i * rectWidth - 0.01
|
||||||
|
startYPos = minY + j * rectHeight + 0.01
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos + j * cell.padding //위에서 밑으로라 +
|
||||||
|
}
|
||||||
|
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos + rectHeight },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos + rectHeight },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startXPos = maxX - i * rectWidth - 0.01
|
||||||
|
startYPos = maxY - j * rectHeight - 0.01
|
||||||
|
|
||||||
|
if (j > 0) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
rectPoints.push(
|
||||||
|
{ x: startXPos, y: startYPos },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos },
|
||||||
|
{ x: startXPos, y: startYPos - rectHeight },
|
||||||
|
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
||||||
|
)
|
||||||
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
||||||
|
}
|
||||||
|
startXPos = startXPos - rectWidth // 우측에서 -> 좌측으로 그려짐
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// centerWidth = 0 //나중에 중간 정렬 이면 어쩌구 함수 만들어서 넣음
|
||||||
|
if (['left', 'right'].includes(cell.wallDirection)) {
|
||||||
|
centerWidth = 0
|
||||||
|
} else if (['top', 'bottom'].includes(cell.wallDirection)) {
|
||||||
|
centerHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.wallDirection === 'left') {
|
||||||
|
startXPos = minX + i * rectWidth + centerWidth
|
||||||
|
startYPos = minY + j * rectHeight + centerHeight
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.wallDirection === 'right') {
|
||||||
|
startXPos = maxX - (1 + i) * rectWidth - 0.01 - centerWidth
|
||||||
|
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos - i * cell.padding
|
||||||
|
}
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.wallDirection === 'top') {
|
||||||
|
startXPos = minX + i * rectWidth - 0.01 + centerWidth
|
||||||
|
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos + j * cell.padding
|
||||||
|
}
|
||||||
|
} else if (cell.wallDirection === 'bottom') {
|
||||||
|
startXPos = minX + i * rectWidth + 0.01 + centerWidth
|
||||||
|
startYPos = maxY - (j + 1) * rectHeight - 0.01 - centerHeight
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
startXPos = startXPos + i * cell.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > 0 && j < rows) {
|
||||||
|
startYPos = startYPos - j * cell.padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allPointsInside = rectPoints.every((point) => this.inPolygonABType(point.x, point.y, points))
|
||||||
|
|
||||||
if (allPointsInside) {
|
if (allPointsInside) {
|
||||||
|
//먼저 그룹화를 시켜놓고 뒤에서 글씨를 넣어서 변경한다
|
||||||
|
const text = new fabric.Text(``, {
|
||||||
|
fontFamily: 'serif',
|
||||||
|
fontSize: 30,
|
||||||
|
fill: 'black',
|
||||||
|
type: 'cellText',
|
||||||
|
})
|
||||||
|
|
||||||
const rect = new fabric.Rect({
|
const rect = new fabric.Rect({
|
||||||
left: startXPos,
|
// left: startXPos,
|
||||||
top: startYPos,
|
// top: startYPos,
|
||||||
width: rectWidth,
|
width: rectWidth,
|
||||||
height: rectHeight,
|
height: rectHeight,
|
||||||
fill: '#BFFD9F',
|
fill: '#BFFD9F',
|
||||||
@ -358,15 +525,22 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
name: 'cell',
|
name: 'cell',
|
||||||
idx: idx,
|
idx: idx,
|
||||||
|
type: 'cellRect',
|
||||||
|
})
|
||||||
|
|
||||||
|
var group = new fabric.Group([rect, text], {
|
||||||
|
left: startXPos,
|
||||||
|
top: startYPos,
|
||||||
})
|
})
|
||||||
|
|
||||||
idx++
|
idx++
|
||||||
drawCellsArray.push(rect) //배열에 넣어서 반환한다
|
drawCellsArray.push(group) //배열에 넣어서 반환한다
|
||||||
this.canvas.add(rect)
|
this.canvas.add(group)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.canvas?.renderAll()
|
this.canvas?.renderAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.cells = drawCellsArray
|
this.cells = drawCellsArray
|
||||||
return drawCellsArray
|
return drawCellsArray
|
||||||
},
|
},
|
||||||
@ -402,6 +576,57 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
return intersects % 2 === 1
|
return intersects % 2 === 1
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inPolygonABType(x, y, polygon) {
|
||||||
|
let inside = false
|
||||||
|
let n = polygon.length
|
||||||
|
|
||||||
|
for (let i = 0, j = n - 1; i < n; j = i++) {
|
||||||
|
let xi = polygon[i].x
|
||||||
|
let yi = polygon[i].y
|
||||||
|
let xj = polygon[j].x
|
||||||
|
let yj = polygon[j].y
|
||||||
|
|
||||||
|
// console.log('xi : ', xi, 'yi : ', yi, 'xj : ', xj, 'yj : ', yj)
|
||||||
|
|
||||||
|
let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
|
||||||
|
if (intersect) inside = !inside
|
||||||
|
}
|
||||||
|
|
||||||
|
return inside
|
||||||
|
},
|
||||||
|
|
||||||
|
inPolygon2(rectPoints) {
|
||||||
|
const polygonCoords = toGeoJSON(this.points)
|
||||||
|
const rectCoords = toGeoJSON(rectPoints)
|
||||||
|
|
||||||
|
const outerPolygon = turf.polygon([polygonCoords])
|
||||||
|
const innerPolygon = turf.polygon([rectCoords])
|
||||||
|
// 각 점이 다각형 내부에 있는지 확인
|
||||||
|
const allPointsInside = rectCoords.every((coord) => {
|
||||||
|
const point = turf.point(coord)
|
||||||
|
return turf.booleanPointInPolygon(point, outerPolygon)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 사각형의 변 정의
|
||||||
|
const rectEdges = [
|
||||||
|
[rectCoords[0], rectCoords[1]],
|
||||||
|
[rectCoords[1], rectCoords[2]],
|
||||||
|
[rectCoords[2], rectCoords[3]],
|
||||||
|
[rectCoords[3], rectCoords[0]],
|
||||||
|
]
|
||||||
|
|
||||||
|
// 다각형의 변 정의
|
||||||
|
const outerEdges = turf.lineString(outerPolygon.geometry.coordinates[0])
|
||||||
|
|
||||||
|
// 사각형의 변들이 다각형의 변과 교차하는지 확인
|
||||||
|
const noEdgesIntersect = rectEdges.every((edge) => {
|
||||||
|
const line = turf.lineString(edge)
|
||||||
|
const intersects = turf.lineIntersect(line, outerEdges)
|
||||||
|
return intersects.features.length === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return allPointsInside && noEdgesIntersect
|
||||||
|
},
|
||||||
distanceFromEdge(point) {
|
distanceFromEdge(point) {
|
||||||
const vertices = this.getCurrentPoints()
|
const vertices = this.getCurrentPoints()
|
||||||
let minDistance = Infinity
|
let minDistance = Infinity
|
||||||
|
|||||||
67
src/hooks/useAxios.js
Normal file
67
src/hooks/useAxios.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export const AxiosType = {
|
||||||
|
INTERNAL: 'Internal',
|
||||||
|
EXTERNAL: 'External',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAxios() {
|
||||||
|
const getInstances = (type) => {
|
||||||
|
return axios.create({
|
||||||
|
baseURL: type === AxiosType.INTERNAL ? process.env.NEXT_PUBLIC_API_SERVER_PATH : '',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.interceptors.request.use((config) => {
|
||||||
|
// config['Authorization'] = localStorage.getItem('token')
|
||||||
|
//TODO: 인터셉터에서 추가 로직 구현
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
axios.interceptors.request.use(undefined, (error) => {
|
||||||
|
//TODO: 인터셉터에서 에러 처리 로직 구현
|
||||||
|
// if (error.isAxiosError && e.response?.status === 401) {
|
||||||
|
// localStorage.removeItem('token')
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
|
const get = async ({ type, url }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.get(url)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = async ({ type, url, data }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.post(url, data)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const put = async ({ type, url, data }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.put(url, data)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const patch = async ({ type, url, data }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.patch(url, data)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = async ({ type, url }) => {
|
||||||
|
return await getInstances(type)
|
||||||
|
.delete(url)
|
||||||
|
.then((res) => res.data)
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { get, post, put, patch, del }
|
||||||
|
}
|
||||||
@ -89,8 +89,8 @@ export function useCanvas(id) {
|
|||||||
canvas?.off('object:modified')
|
canvas?.off('object:modified')
|
||||||
canvas?.off('object:removed')
|
canvas?.off('object:removed')
|
||||||
canvas?.off('object:added')
|
canvas?.off('object:added')
|
||||||
canvas?.off('mouse:move', drawMouseLines)
|
canvas?.off('mouse:move')
|
||||||
canvas?.off('mouse:down', handleMouseDown)
|
canvas?.off('mouse:down')
|
||||||
}
|
}
|
||||||
|
|
||||||
const addEventOnObject = (e) => {
|
const addEventOnObject = (e) => {
|
||||||
@ -112,9 +112,11 @@ export function useCanvas(id) {
|
|||||||
target.on('mousedown', () => {
|
target.on('mousedown', () => {
|
||||||
if (target.get('selected')) {
|
if (target.get('selected')) {
|
||||||
target.set({ strokeWidth: 1 })
|
target.set({ strokeWidth: 1 })
|
||||||
|
target.set({ strokeDashArray: [5, 5] })
|
||||||
target.set({ selected: false })
|
target.set({ selected: false })
|
||||||
} else {
|
} else {
|
||||||
target.set({ strokeWidth: 5 })
|
target.set({ strokeWidth: 5 })
|
||||||
|
target.set({ strokeDashArray: [0, 0] })
|
||||||
target.set({ selected: true })
|
target.set({ selected: true })
|
||||||
}
|
}
|
||||||
canvas?.renderAll()
|
canvas?.renderAll()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
35
src/hooks/useToast.js
Normal file
35
src/hooks/useToast.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
const toastDefaultOptions = {
|
||||||
|
position: 'top-right',
|
||||||
|
autoClose: 3000,
|
||||||
|
draggable: false,
|
||||||
|
hideProgressBar: false,
|
||||||
|
rtl: false,
|
||||||
|
pauseOnFocusLoss: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
theme: 'light',
|
||||||
|
limit: 2,
|
||||||
|
closeOnClick: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const QToast = (props) => {
|
||||||
|
// type TypeOptions = 'info' | 'success' | 'warning' | 'error' | 'default'
|
||||||
|
const { message, type = 'info', options } = props
|
||||||
|
const customOptions = { ...toastDefaultOptions, ...options }
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'info':
|
||||||
|
return toast.info(message, customOptions)
|
||||||
|
case 'success':
|
||||||
|
return toast.success(message, customOptions)
|
||||||
|
case 'warning':
|
||||||
|
return toast.warn(message, customOptions)
|
||||||
|
case 'error':
|
||||||
|
return toast.error(message, customOptions)
|
||||||
|
default:
|
||||||
|
return toast(message, customOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { QToast }
|
||||||
@ -67,3 +67,9 @@ export const roofMaterialState = atom({
|
|||||||
default: { width: 20, height: 10, rafter: 0, roofStyle: 2 },
|
default: { width: 20, height: 10, rafter: 0, roofStyle: 2 },
|
||||||
dangerouslyAllowMutability: true,
|
dangerouslyAllowMutability: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const compassState = atom({
|
||||||
|
key: 'compass',
|
||||||
|
default: undefined,
|
||||||
|
dangerouslyAllowMutability: true,
|
||||||
|
})
|
||||||
|
|||||||
15
src/store/modalAtom.js
Normal file
15
src/store/modalAtom.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { atom } from 'recoil'
|
||||||
|
|
||||||
|
export const modalState = atom({
|
||||||
|
key: 'modalState',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const modalContent = atom({
|
||||||
|
key: 'modalContent',
|
||||||
|
default: (
|
||||||
|
<>
|
||||||
|
<div>test</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
})
|
||||||
@ -1 +1,2 @@
|
|||||||
@import '_test.scss';
|
@import '_test.scss';
|
||||||
|
@import 'react-toastify/dist/ReactToastify.css';
|
||||||
@ -9,6 +9,7 @@ import {
|
|||||||
getRoofHypotenuse,
|
getRoofHypotenuse,
|
||||||
} from '@/util/canvas-util'
|
} from '@/util/canvas-util'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
import * as turf from '@turf/turf'
|
||||||
|
|
||||||
const TWO_PI = Math.PI * 2
|
const TWO_PI = Math.PI * 2
|
||||||
|
|
||||||
@ -983,6 +984,43 @@ export const splitPolygonWithLines = (polygon) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 좌표 테스트용
|
||||||
|
*/
|
||||||
|
allLines.forEach((line) => {
|
||||||
|
const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, {
|
||||||
|
left: line.startPoint.x,
|
||||||
|
top: line.startPoint.y,
|
||||||
|
fontSize: 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
polygon.canvas.add(text)
|
||||||
|
polygon.canvas.renderAll()
|
||||||
|
|
||||||
|
const text2 = new fabric.Text(`(${line.endPoint.x},${line.endPoint.y})`, {
|
||||||
|
left: line.endPoint.x,
|
||||||
|
top: line.endPoint.y,
|
||||||
|
fontSize: 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
polygon.canvas.add(text2)
|
||||||
|
polygon.canvas.renderAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
polygon.points.forEach((point, index) => {
|
||||||
|
const text = new fabric.Text(`(${point.x},${point.y})`, {
|
||||||
|
left: point.x,
|
||||||
|
top: point.y,
|
||||||
|
fontSize: 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
polygon.canvas.add(text)
|
||||||
|
polygon.canvas.renderAll()
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 좌표 테스트용 끝
|
||||||
|
*/
|
||||||
|
|
||||||
polygon.points.forEach((point, index) => {
|
polygon.points.forEach((point, index) => {
|
||||||
allLines.forEach((line) => {
|
allLines.forEach((line) => {
|
||||||
if (line.endPoint.x === point.x && line.endPoint.y === point.y) {
|
if (line.endPoint.x === point.x && line.endPoint.y === point.y) {
|
||||||
@ -1007,22 +1045,39 @@ export const splitPolygonWithLines = (polygon) => {
|
|||||||
const arrivalPoint = endLine.endPoint
|
const arrivalPoint = endLine.endPoint
|
||||||
routes.push(startLine.startPoint)
|
routes.push(startLine.startPoint)
|
||||||
routes.push(startLine.endPoint)
|
routes.push(startLine.endPoint)
|
||||||
|
|
||||||
//hip끼리 만나는 경우는 아무것도 안해도됨
|
//hip끼리 만나는 경우는 아무것도 안해도됨
|
||||||
let count = 0
|
|
||||||
if (!isSamePoint(startLine.endPoint, arrivalPoint)) {
|
if (!isSamePoint(startLine.endPoint, arrivalPoint)) {
|
||||||
// polygon line까지 추가
|
// polygon line까지 추가
|
||||||
const allLinesCopy = [...allLines, ...polygon.lines]
|
const allLinesCopy = [...allLines, ...polygon.lines]
|
||||||
// hip이 만나지 않는 경우 갈 수 있는 길을 다 돌아야함
|
// hip이 만나지 않는 경우 갈 수 있는 길을 다 돌아야함
|
||||||
let currentPoint = startLine.endPoint
|
let currentPoint = startLine.endPoint
|
||||||
let currentLine = startLine
|
let currentLine = startLine
|
||||||
|
let movedLines = []
|
||||||
while (!isSamePoint(currentPoint, arrivalPoint) && count <= polygon.points.length) {
|
let subMovedLines = []
|
||||||
count++
|
while (!isSamePoint(currentPoint, arrivalPoint)) {
|
||||||
// startHip에서 만나는 출발선 두개. 두개의 선을 출발하여 arrivalPoint에 도착할 때 까지 count를 세고, 더 낮은 count를 가진 길을 선택한다.
|
// startHip에서 만나는 출발선 두개. 두개의 선을 출발하여 arrivalPoint에 도착할 때 까지 count를 세고, 더 낮은 count를 가진 길을 선택한다.
|
||||||
let connectedLines = allLinesCopy.filter((line) => isSamePoint(line.startPoint, currentPoint) || isSamePoint(line.endPoint, currentPoint))
|
let connectedLines = allLinesCopy.filter((line) => isSamePoint(line.startPoint, currentPoint) || isSamePoint(line.endPoint, currentPoint))
|
||||||
|
|
||||||
connectedLines = connectedLines.filter((line) => line !== currentLine)
|
connectedLines = connectedLines.filter((line) => line !== currentLine)
|
||||||
|
|
||||||
|
connectedLines = connectedLines.filter((line) => !subMovedLines.includes(line))
|
||||||
|
|
||||||
|
//마지막 선이 endLine의 startPoint와 같은경우 그 전까지 movedLine을 제거한다.
|
||||||
|
const endLineMeetLineCnt = connectedLines.filter((line) => {
|
||||||
|
return isSamePoint(line.endPoint, endLine.startPoint) || isSamePoint(line.startPoint, endLine.startPoint)
|
||||||
|
}).length
|
||||||
|
|
||||||
|
if (endLineMeetLineCnt !== 0) {
|
||||||
|
movedLines.push(subMovedLines)
|
||||||
|
|
||||||
|
console.log(movedLines, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedLines = connectedLines.filter((line) => {
|
||||||
|
return !isSamePoint(line.endPoint, endLine.startPoint) && !isSamePoint(line.startPoint, endLine.startPoint)
|
||||||
|
})
|
||||||
|
|
||||||
if (connectedLines.length === 0) {
|
if (connectedLines.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1050,14 +1105,15 @@ export const splitPolygonWithLines = (polygon) => {
|
|||||||
|
|
||||||
currentPoint = tempPoints[minIndex].point
|
currentPoint = tempPoints[minIndex].point
|
||||||
currentLine = tempPoints[minIndex].line
|
currentLine = tempPoints[minIndex].line
|
||||||
|
if (currentLine !== startLine) {
|
||||||
|
subMovedLines.push(currentLine)
|
||||||
|
}
|
||||||
routes.push(currentPoint)
|
routes.push(currentPoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count <= polygon.points.length - 1) {
|
|
||||||
routes.push(endLine.startPoint)
|
routes.push(endLine.startPoint)
|
||||||
roofs.push(routes)
|
roofs.push(routes)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 중복 제거
|
// 중복 제거
|
||||||
@ -1078,6 +1134,7 @@ export const splitPolygonWithLines = (polygon) => {
|
|||||||
fill: 'transparent',
|
fill: 'transparent',
|
||||||
strokeWidth: 3,
|
strokeWidth: 3,
|
||||||
name: 'roof',
|
name: 'roof',
|
||||||
|
selectable: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
polygon.canvas.add(roof)
|
polygon.canvas.add(roof)
|
||||||
@ -2737,3 +2794,35 @@ function arraysHaveSamePoints(array1, array2) {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toGeoJSON = (pointsArray) => {
|
||||||
|
// 객체 배열을 GeoJSON 형식의 좌표 배열로 변환
|
||||||
|
const coordinates = pointsArray.map((point) => [point.x, point.y])
|
||||||
|
|
||||||
|
// 닫힌 다각형을 만들기 위해 첫 번째 점을 마지막에 추가
|
||||||
|
coordinates.push([pointsArray[0].x, pointsArray[0].y])
|
||||||
|
|
||||||
|
return coordinates
|
||||||
|
}
|
||||||
|
|
||||||
|
export const inPolygon = (polygonPoints, rectPoints) => {
|
||||||
|
const polygonCoordinates = toGeoJSON(polygonPoints)
|
||||||
|
const rectCoordinates = toGeoJSON(rectPoints)
|
||||||
|
|
||||||
|
const polygonFeature = turf.polygon([polygonCoordinates])
|
||||||
|
const rectFeature = turf.polygon([rectCoordinates])
|
||||||
|
|
||||||
|
// 사각형의 모든 꼭짓점이 다각형 내부에 있는지 확인
|
||||||
|
const allPointsInsidePolygon = rectCoordinates.every((coord) => {
|
||||||
|
const point = turf.point(coord)
|
||||||
|
return turf.booleanPointInPolygon(point, polygonFeature)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 다각형의 모든 점이 사각형 내부에 있지 않은지 확인
|
||||||
|
const noPolygonPointsInsideRect = polygonCoordinates.every((coord) => {
|
||||||
|
const point = turf.point(coord)
|
||||||
|
return !turf.booleanPointInPolygon(point, rectFeature)
|
||||||
|
})
|
||||||
|
|
||||||
|
return allPointsInsidePolygon && noPolygonPointsInsideRect
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user