@operato/weather 9.0.0-beta.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +337 -0
- package/assets/images/no-image.png +0 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/layers/base-weather-layer.d.ts +56 -0
- package/dist/src/layers/base-weather-layer.js +83 -0
- package/dist/src/layers/base-weather-layer.js.map +1 -0
- package/dist/src/layers/cloud-layer.d.ts +17 -0
- package/dist/src/layers/cloud-layer.js +203 -0
- package/dist/src/layers/cloud-layer.js.map +1 -0
- package/dist/src/layers/precipitation-layer.d.ts +27 -0
- package/dist/src/layers/precipitation-layer.js +344 -0
- package/dist/src/layers/precipitation-layer.js.map +1 -0
- package/dist/src/layers/solar-layer.d.ts +17 -0
- package/dist/src/layers/solar-layer.js +253 -0
- package/dist/src/layers/solar-layer.js.map +1 -0
- package/dist/src/layers/temperature-layer.d.ts +13 -0
- package/dist/src/layers/temperature-layer.js +230 -0
- package/dist/src/layers/temperature-layer.js.map +1 -0
- package/dist/src/layers/wind-layer.d.ts +60 -0
- package/dist/src/layers/wind-layer.js +311 -0
- package/dist/src/layers/wind-layer.js.map +1 -0
- package/dist/src/ox-weather.d.ts +112 -0
- package/dist/src/ox-weather.js +645 -0
- package/dist/src/ox-weather.js.map +1 -0
- package/dist/src/ox-wind.d.ts +91 -0
- package/dist/src/ox-wind.js +425 -0
- package/dist/src/ox-wind.js.map +1 -0
- package/dist/src/services/weather-service.d.ts +79 -0
- package/dist/src/services/weather-service.js +560 -0
- package/dist/src/services/weather-service.js.map +1 -0
- package/dist/src/types.d.ts +71 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/stories/data-weather.stories.d.ts +47 -0
- package/dist/stories/data-weather.stories.js +93 -0
- package/dist/stories/data-weather.stories.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +86 -0
- package/themes/app-theme.css +138 -0
- package/themes/calendar-theme.css +61 -0
- package/themes/dark.css +51 -0
- package/themes/form-theme.css +70 -0
- package/themes/grist-theme.css +175 -0
- package/themes/layout-theme.css +94 -0
- package/themes/light.css +51 -0
- package/themes/material-theme.css +23 -0
- package/themes/md-typescale-styles.css +100 -0
- package/themes/oops-theme.css +22 -0
- package/themes/report-theme.css +47 -0
- package/themes/spacing.css +23 -0
- package/themes/state-color.css +6 -0
- package/themes/tooltip-theme.css +11 -0
- package/translations/en.json +1 -0
- package/translations/ja.json +1 -0
- package/translations/ko.json +1 -0
- package/translations/ms.json +1 -0
- package/translations/zh.json +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precipitation-layer.js","sourceRoot":"","sources":["../../../src/layers/precipitation-layer.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseWeatherLayer } from './base-weather-layer'\nimport { WeatherData, WeatherLayerType } from '../types'\nimport BaseLayer from 'ol/layer/Base'\nimport VectorSource from 'ol/source/Vector'\nimport VectorLayer from 'ol/layer/Vector'\nimport Feature from 'ol/Feature'\nimport Point from 'ol/geom/Point'\nimport { fromLonLat } from 'ol/proj'\nimport { Style, Icon, Text, Fill, Stroke } from 'ol/style'\nimport Polygon from 'ol/geom/Polygon'\n\n/**\n * 강수량 레이어 구현\n */\nexport class PrecipitationLayer extends BaseWeatherLayer {\n private animationFrameId?: number\n private animationTime: number = 0\n\n getType(): WeatherLayerType {\n return 'precipitation'\n }\n\n /**\n * 벡터 레이어를 생성하여 강수량을 표현합니다.\n */\n createLayer(): BaseLayer {\n const source = new VectorSource()\n\n if (!this.weatherData || this.weatherData.length === 0) {\n return new VectorLayer({\n source,\n zIndex: 12\n })\n }\n\n // 강수량 데이터 추출 - 강수 확률이 일정 이상인 경우만 고려\n const validData = this.weatherData.filter(data => data.precipitation.probability >= 20)\n\n console.log(\n '강수 데이터 포인트:',\n validData.length,\n '첫 번째 포인트:',\n validData.length > 0 ? validData[0] : 'none'\n )\n\n if (validData.length === 0) {\n return new VectorLayer({\n source,\n zIndex: 12\n })\n }\n\n // 각 데이터 포인트의 위치와 강수량 추출\n const points = validData.map(data => ({\n lon: data.location.longitude,\n lat: data.location.latitude,\n amount: data.precipitation.amount,\n probability: data.precipitation.probability\n }))\n\n // 강수량 최대/최소값 계산\n let minAmount = Infinity\n let maxAmount = -Infinity\n\n points.forEach(point => {\n // 강수 확률을 가중치로 사용하여 실효 강수량 계산\n const effectiveAmount = point.amount * (point.probability / 100)\n minAmount = Math.min(minAmount, effectiveAmount)\n maxAmount = Math.max(maxAmount, effectiveAmount)\n })\n\n console.log('강수량 최소/최대값:', minAmount, maxAmount)\n\n // 강수량 정규화 함수 - 0~1 범위로 변환\n const normalizeAmount = (amount: number) => {\n if (maxAmount === minAmount) return 0.5\n return (amount - minAmount) / (maxAmount - minAmount)\n }\n\n // 개선된 강수량 보간 함수 - IDW(Inverse Distance Weighting) 기반 보간법\n const interpolateAmount = (lon: number, lat: number) => {\n // 가장 가까운 이웃 개수 제한 - 모든 포인트 대신 가까운 포인트만 사용하여 국지적 패턴 유지\n const MAX_NEIGHBORS = 5\n\n // 현재 위치에서 모든 포인트까지의 거리 계산\n const distances = points.map(point => {\n const dx = lon - point.lon\n const dy = lat - point.lat\n return {\n point,\n dist: Math.sqrt(dx * dx + dy * dy)\n }\n })\n\n // 거리에 따라 정렬하고 가장 가까운 이웃만 선택\n const nearestNeighbors = distances.sort((a, b) => a.dist - b.dist).slice(0, MAX_NEIGHBORS)\n\n // 포인트가 정확히 일치하는 경우\n if (nearestNeighbors.length > 0 && nearestNeighbors[0].dist < 0.0001) {\n // 강수 확률을 가중치로 사용하여 실효 강수량 계산\n return nearestNeighbors[0].point.amount * (nearestNeighbors[0].point.probability / 100)\n }\n\n // 역거리 가중치 보간법 적용 - 거리의 제곱에 반비례하는 가중치 사용\n let totalWeight = 0\n let weightedAmount = 0\n\n // 가중치 지수 - 낮을수록 더 부드러운 보간, 높을수록 가까운 값에 더 큰 영향\n const power = 2.0 // 강수는 지역적 특성이 강하므로 더 높은 지수 사용\n\n nearestNeighbors.forEach(({ point, dist }) => {\n // 작은 값으로 나누기 방지\n const adjustedDist = Math.max(dist, 0.0001)\n const weight = 1 / Math.pow(adjustedDist, power)\n\n // 강수 확률을 가중치로 사용하여 실효 강수량 계산\n const effectiveAmount = point.amount * (point.probability / 100)\n\n totalWeight += weight\n weightedAmount += effectiveAmount * weight\n })\n\n return weightedAmount / totalWeight\n }\n\n // 강수량에 따른 색상 계산 함수\n const getColorFromAmount = (amount: number, time: number = 0) => {\n const normalized = normalizeAmount(amount)\n\n if (normalized < 0.01) return 'rgba(0,0,0,0)' // 너무 적은 강수량은 투명하게\n\n // 강수량에 따른 색상 설정 (파란색 계열)\n const hue = 210 - normalized * 30 // 210~180 범위의 파란색\n const saturation = 60 + normalized * 40 // 60~100% 채도\n const lightness = 60 - normalized * 30 // 60~30% 밝기\n\n // 강수량에 따라 투명도 조정\n const alpha = 0.3 + normalized * 0.5\n\n // 애니메이션 효과 (시간에 따라 약간의 투명도 변화)\n const animAlpha = alpha * (0.8 + Math.sin(time * 3) * 0.2)\n\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${animAlpha})`\n }\n\n // 그라데이션을 적용할 그리드 생성\n const bounds = {\n minLon: Infinity,\n maxLon: -Infinity,\n minLat: Infinity,\n maxLat: -Infinity\n }\n\n // 데이터 포인트로부터 영역 경계 계산\n points.forEach(point => {\n bounds.minLon = Math.min(bounds.minLon, point.lon)\n bounds.maxLon = Math.max(bounds.maxLon, point.lon)\n bounds.minLat = Math.min(bounds.minLat, point.lat)\n bounds.maxLat = Math.max(bounds.maxLat, point.lat)\n })\n\n // 경계에 마진 추가\n const margin = 0.1\n bounds.minLon -= margin\n bounds.maxLon += margin\n bounds.minLat -= margin\n bounds.maxLat += margin\n\n // 고해상도 그리드 생성 (더 매끄러운 그라데이션을 위해)\n const gridResolution = 80 // 적절한 해상도로 조정\n const lonStep = (bounds.maxLon - bounds.minLon) / gridResolution\n const latStep = (bounds.maxLat - bounds.minLat) / gridResolution\n\n // 인접 셀 간의 값 차이 최소화를 위한 스무딩 함수\n const smoothGrid = (grid: number[][], passes: number = 2): number[][] => {\n const rows = grid.length\n if (rows === 0) return grid\n\n const cols = grid[0].length\n const result = Array(rows)\n .fill(0)\n .map(() => Array(cols).fill(0))\n\n // 여러 번 스무딩 패스 적용\n for (let pass = 0; pass < passes; pass++) {\n const currentGrid = pass === 0 ? grid : result\n\n for (let i = 0; i < rows; i++) {\n for (let j = 0; j < cols; j++) {\n // 현재 셀과 인접 셀의 평균 계산\n let sum = currentGrid[i][j]\n let count = 1\n\n // 상하좌우 셀 확인\n const neighbors = [\n [i - 1, j],\n [i + 1, j],\n [i, j - 1],\n [i, j + 1]\n ]\n\n neighbors.forEach(([ni, nj]) => {\n if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) {\n sum += currentGrid[ni][nj]\n count++\n }\n })\n\n // 평균값으로 스무딩 (약간의 원본 가중치 부여)\n result[i][j] = currentGrid[i][j] * 0.7 + ((sum - currentGrid[i][j]) / (count - 1)) * 0.3\n }\n }\n }\n\n return result\n }\n\n // 강수량 데이터 그리드 생성\n const precipGrid: number[][] = []\n\n for (let i = 0; i < gridResolution; i++) {\n precipGrid[i] = []\n for (let j = 0; j < gridResolution; j++) {\n const centerLon = bounds.minLon + (j + 0.5) * lonStep\n const centerLat = bounds.minLat + (i + 0.5) * latStep\n precipGrid[i][j] = interpolateAmount(centerLon, centerLat)\n }\n }\n\n // 그리드 스무딩 적용 - 강수는 더 선명한 경계를 위해 약한 스무딩만 적용\n const smoothedGrid = smoothGrid(precipGrid, 1)\n\n // 셀 생성 및 피처 추가\n for (let i = 0; i < gridResolution; i++) {\n for (let j = 0; j < gridResolution; j++) {\n const lonMin = bounds.minLon + j * lonStep\n const lonMax = bounds.minLon + (j + 1) * lonStep\n const latMin = bounds.minLat + i * latStep\n const latMax = bounds.minLat + (i + 1) * latStep\n\n // 스무딩된 값 사용\n const interpolatedAmount = smoothedGrid[i][j]\n\n // 너무 적은 강수량은 제외\n if (interpolatedAmount < minAmount + (maxAmount - minAmount) * 0.05) continue\n\n // 셀 좌표 계산 및 변환\n const coordinates = [\n [lonMin, latMin],\n [lonMax, latMin],\n [lonMax, latMax],\n [lonMin, latMax],\n [lonMin, latMin]\n ].map(coord => fromLonLat(coord))\n\n // 다각형 피처 생성\n const feature = new Feature({\n geometry: new Polygon([coordinates]),\n amount: interpolatedAmount,\n // 애니메이션을 위한 랜덤 오프셋\n offset: Math.random()\n })\n\n source.addFeature(feature)\n }\n }\n\n // CSS 블렌딩 모드 정의를 위한 클래스 이름 추가\n const layerClass = 'precipitation-layer'\n\n // 블렌딩 모드 스타일 추가\n if (!document.getElementById('precipitation-layer-style')) {\n const style = document.createElement('style')\n style.id = 'precipitation-layer-style'\n style.textContent = `\n .${layerClass} canvas {\n mix-blend-mode: multiply;\n }\n `\n document.head.appendChild(style)\n }\n\n // 벡터 레이어 생성\n return new VectorLayer({\n source,\n zIndex: 12,\n className: layerClass,\n style: feature => {\n const amount = feature.get('amount')\n const offset = feature.get('offset')\n\n // 애니메이션 시간 (오프셋을 통해 각 위치마다 다른 타이밍을 가짐)\n const time = this.animationTime + offset * 5\n\n return new Style({\n fill: new Fill({\n color: getColorFromAmount(amount, time)\n }),\n // 경계선 완전히 제거\n stroke: undefined\n })\n }\n })\n }\n\n /**\n * 애니메이션을 시작합니다.\n */\n startAnimation() {\n if (this.animationFrameId) {\n this.stopAnimation()\n }\n\n this.animationTime = 0\n let lastFrameTime = 0\n const targetFPS = 30 // 초당 프레임 수 제한 (60fps → 30fps)\n const frameInterval = 1000 / targetFPS\n\n const animate = (timestamp: number) => {\n // 프레임 간 시간 제어\n if (!lastFrameTime) lastFrameTime = timestamp\n\n const deltaTime = timestamp - lastFrameTime\n\n // 목표 프레임 간격보다 적게 지났으면 다음 프레임으로 넘어감\n if (deltaTime < frameInterval) {\n this.animationFrameId = requestAnimationFrame(animate)\n return\n }\n\n // 지난 시간만큼 애니메이션 진행\n const delta = Math.min(deltaTime / 1000, 0.1) // 최대 0.1초로 제한하여 큰 지연 방지\n this.animationTime += delta * 0.5 // 속도 조절\n\n lastFrameTime = timestamp\n\n // 맵 인스턴스 확인 (존재하지 않으면 애니메이션 종료)\n if (!this.map) {\n this.stopAnimation()\n return\n }\n\n // 지도가 현재 보이는지 확인 (화면 밖이면 렌더링 넘어감)\n const mapElement = this.map.getTargetElement()\n if (mapElement) {\n const rect = mapElement.getBoundingClientRect()\n // 화면에 완전히 보이지 않으면 렌더링 건너뛰기\n const isVisible =\n rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0\n\n // 화면에 보일 때만 렌더링\n if (isVisible) {\n this.map.render()\n }\n }\n\n // 다음 프레임 예약\n this.animationFrameId = requestAnimationFrame(animate)\n }\n\n this.animationFrameId = requestAnimationFrame(animate)\n }\n\n /**\n * 애니메이션을 중지합니다.\n */\n stopAnimation() {\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId)\n this.animationFrameId = undefined\n }\n }\n\n /**\n * 레이어에 날씨 데이터를 설정합니다.\n */\n setWeatherData(data: WeatherData[]) {\n this.weatherData = data\n this.updateLayer()\n\n // 데이터가 변경되면 애니메이션 재시작\n this.startAnimation()\n }\n}\n\n// 강수량 표시를 위한 스타일링\nconst createPrecipitationFeatureStyle = (feature: Feature<Point>, resolution: number): Style => {\n const properties = feature.getProperties()\n const precipitation = properties.precipitation\n\n // 강수량이 없으면 빈 스타일 반환\n if (!precipitation || precipitation === 0) {\n return new Style({})\n }\n\n // 아이콘과 텍스트 스타일 생성\n return new Style({\n image: new Icon({\n src: 'data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"%230000ff\" d=\"M12 4c4.41 0 8 3.59 8 8s-3.59 8-8 8s-8-3.59-8-8s3.59-8 8-8m0-2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 14l-4-4h8z\"/></svg>',\n scale: 0.8,\n opacity: 0.8\n }),\n text: new Text({\n text: `${precipitation} mm`,\n font: 'bold 12px Arial',\n fill: new Fill({\n color: 'rgba(0, 0, 0, 0.9)'\n }),\n stroke: new Stroke({\n color: 'rgba(255, 255, 255, 0.8)',\n width: 3\n }),\n offsetY: -15\n }),\n zIndex: 12\n })\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseWeatherLayer } from './base-weather-layer';
|
|
2
|
+
import { WeatherData, WeatherLayerType } from '../types';
|
|
3
|
+
import BaseLayer from 'ol/layer/Base';
|
|
4
|
+
/**
|
|
5
|
+
* 태양광 레이어 구현
|
|
6
|
+
*/
|
|
7
|
+
export declare class SolarLayer extends BaseWeatherLayer {
|
|
8
|
+
getType(): WeatherLayerType;
|
|
9
|
+
/**
|
|
10
|
+
* 벡터 레이어를 생성하여 태양광을 표현합니다.
|
|
11
|
+
*/
|
|
12
|
+
createLayer(): BaseLayer;
|
|
13
|
+
/**
|
|
14
|
+
* 레이어에 날씨 데이터를 설정합니다.
|
|
15
|
+
*/
|
|
16
|
+
setWeatherData(data: WeatherData[]): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { BaseWeatherLayer } from './base-weather-layer';
|
|
2
|
+
import VectorSource from 'ol/source/Vector';
|
|
3
|
+
import VectorLayer from 'ol/layer/Vector';
|
|
4
|
+
import Feature from 'ol/Feature';
|
|
5
|
+
import { fromLonLat } from 'ol/proj';
|
|
6
|
+
import { Style, Fill } from 'ol/style';
|
|
7
|
+
import Polygon from 'ol/geom/Polygon';
|
|
8
|
+
/**
|
|
9
|
+
* 태양광 레이어 구현
|
|
10
|
+
*/
|
|
11
|
+
export class SolarLayer extends BaseWeatherLayer {
|
|
12
|
+
getType() {
|
|
13
|
+
return 'solar';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 벡터 레이어를 생성하여 태양광을 표현합니다.
|
|
17
|
+
*/
|
|
18
|
+
createLayer() {
|
|
19
|
+
const source = new VectorSource();
|
|
20
|
+
console.log('태양광 레이어 생성 - 데이터 존재 여부:', !!this.weatherData, '데이터 개수:', this.weatherData?.length);
|
|
21
|
+
if (!this.weatherData || this.weatherData.length === 0) {
|
|
22
|
+
console.log('태양광 레이어: 날씨 데이터가 없음');
|
|
23
|
+
return new VectorLayer({
|
|
24
|
+
source,
|
|
25
|
+
zIndex: 8
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// 태양광 데이터 유효성 확인
|
|
29
|
+
const validData = this.weatherData.filter(data => {
|
|
30
|
+
const hasSolar = data.solar && typeof data.solar.radiation === 'number' && data.cloud && typeof data.cloud.coverage === 'number';
|
|
31
|
+
if (!hasSolar) {
|
|
32
|
+
console.log('태양광 데이터 없음:', data);
|
|
33
|
+
}
|
|
34
|
+
return hasSolar;
|
|
35
|
+
});
|
|
36
|
+
console.log('유효한 태양광 데이터 수:', validData.length);
|
|
37
|
+
if (validData.length === 0) {
|
|
38
|
+
console.log('태양광 레이어: 유효한 태양광 데이터가 없음');
|
|
39
|
+
return new VectorLayer({
|
|
40
|
+
source,
|
|
41
|
+
zIndex: 8
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// 각 데이터 포인트의 위치와 태양광 값 추출
|
|
45
|
+
const points = validData.map(data => ({
|
|
46
|
+
lon: data.location.longitude,
|
|
47
|
+
lat: data.location.latitude,
|
|
48
|
+
radiation: data.solar.radiation,
|
|
49
|
+
// 구름 영향 고려
|
|
50
|
+
cloudImpact: Math.max(0, 1 - data.cloud.coverage / 100),
|
|
51
|
+
// 구름 영향을 고려한 유효 태양광
|
|
52
|
+
effectiveRadiation: data.solar.radiation * Math.max(0, 1 - data.cloud.coverage / 100)
|
|
53
|
+
}));
|
|
54
|
+
console.log('태양광 데이터 포인트:', points.length, '첫 번째 포인트:', points[0]);
|
|
55
|
+
// 태양광 최대/최소값 계산
|
|
56
|
+
let minRadiation = Infinity;
|
|
57
|
+
let maxRadiation = -Infinity;
|
|
58
|
+
points.forEach(point => {
|
|
59
|
+
minRadiation = Math.min(minRadiation, point.effectiveRadiation);
|
|
60
|
+
maxRadiation = Math.max(maxRadiation, point.effectiveRadiation);
|
|
61
|
+
});
|
|
62
|
+
console.log('태양광 최소/최대값:', minRadiation, maxRadiation);
|
|
63
|
+
// 태양광 정규화 함수 - 0~1 범위로 변환
|
|
64
|
+
const normalizeRadiation = (radiation) => {
|
|
65
|
+
if (maxRadiation === minRadiation)
|
|
66
|
+
return 0.5;
|
|
67
|
+
return (radiation - minRadiation) / (maxRadiation - minRadiation);
|
|
68
|
+
};
|
|
69
|
+
// 개선된 태양광 보간 함수 - IDW(Inverse Distance Weighting) 기반 보간법
|
|
70
|
+
const interpolateRadiation = (lon, lat) => {
|
|
71
|
+
// 가장 가까운 이웃 개수 제한 - 모든 포인트 대신 가까운 포인트만 사용하여 국지적 패턴 유지
|
|
72
|
+
const MAX_NEIGHBORS = 5;
|
|
73
|
+
// 현재 위치에서 모든 포인트까지의 거리 계산
|
|
74
|
+
const distances = points.map(point => {
|
|
75
|
+
const dx = lon - point.lon;
|
|
76
|
+
const dy = lat - point.lat;
|
|
77
|
+
return {
|
|
78
|
+
point,
|
|
79
|
+
dist: Math.sqrt(dx * dx + dy * dy)
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
// 거리에 따라 정렬하고 가장 가까운 이웃만 선택
|
|
83
|
+
const nearestNeighbors = distances.sort((a, b) => a.dist - b.dist).slice(0, MAX_NEIGHBORS);
|
|
84
|
+
// 포인트가 정확히 일치하는 경우
|
|
85
|
+
if (nearestNeighbors.length > 0 && nearestNeighbors[0].dist < 0.0001) {
|
|
86
|
+
return nearestNeighbors[0].point.effectiveRadiation;
|
|
87
|
+
}
|
|
88
|
+
// 역거리 가중치 보간법 적용 - 거리의 제곱에 반비례하는 가중치 사용
|
|
89
|
+
let totalWeight = 0;
|
|
90
|
+
let weightedRadiation = 0;
|
|
91
|
+
// 가중치 지수 - 낮을수록 더 부드러운 보간, 높을수록 가까운 값에 더 큰 영향
|
|
92
|
+
const power = 1.5;
|
|
93
|
+
nearestNeighbors.forEach(({ point, dist }) => {
|
|
94
|
+
// 작은 값으로 나누기 방지
|
|
95
|
+
const adjustedDist = Math.max(dist, 0.0001);
|
|
96
|
+
const weight = 1 / Math.pow(adjustedDist, power);
|
|
97
|
+
totalWeight += weight;
|
|
98
|
+
weightedRadiation += point.effectiveRadiation * weight;
|
|
99
|
+
});
|
|
100
|
+
return weightedRadiation / totalWeight;
|
|
101
|
+
};
|
|
102
|
+
// 태양광에 따른 색상 계산 함수
|
|
103
|
+
const getColorFromRadiation = (radiation) => {
|
|
104
|
+
const normalized = normalizeRadiation(radiation);
|
|
105
|
+
// 태양광 표현에 적합한 색상 - 낮은 값은 더 어두운 노랑, 높은 값은 밝고 강렬한 노랑
|
|
106
|
+
const hue = 50 - normalized * 10; // 50~40 범위의 노란색
|
|
107
|
+
const saturation = 60 + normalized * 40; // 60~100% 채도
|
|
108
|
+
const lightness = 30 + normalized * 60; // 30~90% 밝기
|
|
109
|
+
// 태양광 강도에 따라 투명도 조정 - 강한 태양광은 더 선명하게
|
|
110
|
+
const alpha = 0.3 + normalized * 0.5;
|
|
111
|
+
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
|
|
112
|
+
};
|
|
113
|
+
// 그라데이션을 적용할 그리드 생성
|
|
114
|
+
const bounds = {
|
|
115
|
+
minLon: Infinity,
|
|
116
|
+
maxLon: -Infinity,
|
|
117
|
+
minLat: Infinity,
|
|
118
|
+
maxLat: -Infinity
|
|
119
|
+
};
|
|
120
|
+
// 데이터 포인트로부터 영역 경계 계산
|
|
121
|
+
points.forEach(point => {
|
|
122
|
+
bounds.minLon = Math.min(bounds.minLon, point.lon);
|
|
123
|
+
bounds.maxLon = Math.max(bounds.maxLon, point.lon);
|
|
124
|
+
bounds.minLat = Math.min(bounds.minLat, point.lat);
|
|
125
|
+
bounds.maxLat = Math.max(bounds.maxLat, point.lat);
|
|
126
|
+
});
|
|
127
|
+
// 경계에 마진 추가
|
|
128
|
+
const margin = 0.1;
|
|
129
|
+
bounds.minLon -= margin;
|
|
130
|
+
bounds.maxLon += margin;
|
|
131
|
+
bounds.minLat -= margin;
|
|
132
|
+
bounds.maxLat += margin;
|
|
133
|
+
// 고해상도 그리드 생성 (더 매끄러운 그라데이션을 위해)
|
|
134
|
+
const gridResolution = 100; // 적절한 해상도로 조정
|
|
135
|
+
const lonStep = (bounds.maxLon - bounds.minLon) / gridResolution;
|
|
136
|
+
const latStep = (bounds.maxLat - bounds.minLat) / gridResolution;
|
|
137
|
+
// 인접 셀 간의 값 차이 최소화를 위한 스무딩 함수
|
|
138
|
+
const smoothGrid = (grid, passes = 2) => {
|
|
139
|
+
const rows = grid.length;
|
|
140
|
+
if (rows === 0)
|
|
141
|
+
return grid;
|
|
142
|
+
const cols = grid[0].length;
|
|
143
|
+
const result = Array(rows)
|
|
144
|
+
.fill(0)
|
|
145
|
+
.map(() => Array(cols).fill(0));
|
|
146
|
+
// 여러 번 스무딩 패스 적용
|
|
147
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
148
|
+
const currentGrid = pass === 0 ? grid : result;
|
|
149
|
+
for (let i = 0; i < rows; i++) {
|
|
150
|
+
for (let j = 0; j < cols; j++) {
|
|
151
|
+
// 현재 셀과 인접 셀의 평균 계산
|
|
152
|
+
let sum = currentGrid[i][j];
|
|
153
|
+
let count = 1;
|
|
154
|
+
// 상하좌우 셀 확인
|
|
155
|
+
const neighbors = [
|
|
156
|
+
[i - 1, j],
|
|
157
|
+
[i + 1, j],
|
|
158
|
+
[i, j - 1],
|
|
159
|
+
[i, j + 1]
|
|
160
|
+
];
|
|
161
|
+
neighbors.forEach(([ni, nj]) => {
|
|
162
|
+
if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) {
|
|
163
|
+
sum += currentGrid[ni][nj];
|
|
164
|
+
count++;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// 평균값으로 스무딩 (약간의 원본 가중치 부여)
|
|
168
|
+
result[i][j] = currentGrid[i][j] * 0.6 + ((sum - currentGrid[i][j]) / (count - 1)) * 0.4;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
};
|
|
174
|
+
// 태양광 데이터 그리드 생성
|
|
175
|
+
const radiationGrid = [];
|
|
176
|
+
for (let i = 0; i < gridResolution; i++) {
|
|
177
|
+
radiationGrid[i] = [];
|
|
178
|
+
for (let j = 0; j < gridResolution; j++) {
|
|
179
|
+
const centerLon = bounds.minLon + (j + 0.5) * lonStep;
|
|
180
|
+
const centerLat = bounds.minLat + (i + 0.5) * latStep;
|
|
181
|
+
radiationGrid[i][j] = interpolateRadiation(centerLon, centerLat);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// 그리드 스무딩 적용
|
|
185
|
+
const smoothedGrid = smoothGrid(radiationGrid, 2);
|
|
186
|
+
// 셀 생성 및 피처 추가
|
|
187
|
+
for (let i = 0; i < gridResolution; i++) {
|
|
188
|
+
for (let j = 0; j < gridResolution; j++) {
|
|
189
|
+
const lonMin = bounds.minLon + j * lonStep;
|
|
190
|
+
const lonMax = bounds.minLon + (j + 1) * lonStep;
|
|
191
|
+
const latMin = bounds.minLat + i * latStep;
|
|
192
|
+
const latMax = bounds.minLat + (i + 1) * latStep;
|
|
193
|
+
// 스무딩된 값 사용
|
|
194
|
+
const interpolatedRadiation = smoothedGrid[i][j];
|
|
195
|
+
// 너무 작은 태양광은 제외 (최소값의 5% 미만)
|
|
196
|
+
if (interpolatedRadiation < minRadiation + (maxRadiation - minRadiation) * 0.05)
|
|
197
|
+
continue;
|
|
198
|
+
// 셀 좌표 계산 및 변환
|
|
199
|
+
const coordinates = [
|
|
200
|
+
[lonMin, latMin],
|
|
201
|
+
[lonMax, latMin],
|
|
202
|
+
[lonMax, latMax],
|
|
203
|
+
[lonMin, latMax],
|
|
204
|
+
[lonMin, latMin]
|
|
205
|
+
].map(coord => fromLonLat(coord));
|
|
206
|
+
// 다각형 피처 생성
|
|
207
|
+
const feature = new Feature({
|
|
208
|
+
geometry: new Polygon([coordinates]),
|
|
209
|
+
radiation: interpolatedRadiation
|
|
210
|
+
});
|
|
211
|
+
source.addFeature(feature);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// CSS 블렌딩 모드 정의를 위한 클래스 이름 추가
|
|
215
|
+
const layerClass = 'solar-layer';
|
|
216
|
+
// 블렌딩 모드 스타일 추가
|
|
217
|
+
if (!document.getElementById('solar-layer-style')) {
|
|
218
|
+
const style = document.createElement('style');
|
|
219
|
+
style.id = 'solar-layer-style';
|
|
220
|
+
style.textContent = `
|
|
221
|
+
.${layerClass} canvas {
|
|
222
|
+
mix-blend-mode: overlay;
|
|
223
|
+
}
|
|
224
|
+
`;
|
|
225
|
+
document.head.appendChild(style);
|
|
226
|
+
}
|
|
227
|
+
// 벡터 레이어 생성
|
|
228
|
+
return new VectorLayer({
|
|
229
|
+
source,
|
|
230
|
+
zIndex: 8,
|
|
231
|
+
className: layerClass,
|
|
232
|
+
opacity: 0.8,
|
|
233
|
+
style: feature => {
|
|
234
|
+
const radiation = feature.get('radiation');
|
|
235
|
+
return new Style({
|
|
236
|
+
fill: new Fill({
|
|
237
|
+
color: getColorFromRadiation(radiation)
|
|
238
|
+
}),
|
|
239
|
+
// 경계선 완전히 제거
|
|
240
|
+
stroke: undefined
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 레이어에 날씨 데이터를 설정합니다.
|
|
247
|
+
*/
|
|
248
|
+
setWeatherData(data) {
|
|
249
|
+
this.weatherData = data;
|
|
250
|
+
this.updateLayer();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=solar-layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solar-layer.js","sourceRoot":"","sources":["../../../src/layers/solar-layer.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseWeatherLayer } from './base-weather-layer'\nimport { WeatherData, WeatherLayerType } from '../types'\nimport BaseLayer from 'ol/layer/Base'\nimport VectorSource from 'ol/source/Vector'\nimport VectorLayer from 'ol/layer/Vector'\nimport Feature from 'ol/Feature'\nimport { fromLonLat } from 'ol/proj'\nimport { Style, Fill } from 'ol/style'\nimport Polygon from 'ol/geom/Polygon'\n\n/**\n * 태양광 레이어 구현\n */\nexport class SolarLayer extends BaseWeatherLayer {\n getType(): WeatherLayerType {\n return 'solar'\n }\n\n /**\n * 벡터 레이어를 생성하여 태양광을 표현합니다.\n */\n createLayer(): BaseLayer {\n const source = new VectorSource()\n\n console.log('태양광 레이어 생성 - 데이터 존재 여부:', !!this.weatherData, '데이터 개수:', this.weatherData?.length)\n\n if (!this.weatherData || this.weatherData.length === 0) {\n console.log('태양광 레이어: 날씨 데이터가 없음')\n return new VectorLayer({\n source,\n zIndex: 8\n })\n }\n\n // 태양광 데이터 유효성 확인\n const validData = this.weatherData.filter(data => {\n const hasSolar =\n data.solar && typeof data.solar.radiation === 'number' && data.cloud && typeof data.cloud.coverage === 'number'\n if (!hasSolar) {\n console.log('태양광 데이터 없음:', data)\n }\n return hasSolar\n })\n\n console.log('유효한 태양광 데이터 수:', validData.length)\n\n if (validData.length === 0) {\n console.log('태양광 레이어: 유효한 태양광 데이터가 없음')\n return new VectorLayer({\n source,\n zIndex: 8\n })\n }\n\n // 각 데이터 포인트의 위치와 태양광 값 추출\n const points = validData.map(data => ({\n lon: data.location.longitude,\n lat: data.location.latitude,\n radiation: data.solar.radiation,\n // 구름 영향 고려\n cloudImpact: Math.max(0, 1 - data.cloud.coverage / 100),\n // 구름 영향을 고려한 유효 태양광\n effectiveRadiation: data.solar.radiation * Math.max(0, 1 - data.cloud.coverage / 100)\n }))\n\n console.log('태양광 데이터 포인트:', points.length, '첫 번째 포인트:', points[0])\n\n // 태양광 최대/최소값 계산\n let minRadiation = Infinity\n let maxRadiation = -Infinity\n\n points.forEach(point => {\n minRadiation = Math.min(minRadiation, point.effectiveRadiation)\n maxRadiation = Math.max(maxRadiation, point.effectiveRadiation)\n })\n\n console.log('태양광 최소/최대값:', minRadiation, maxRadiation)\n\n // 태양광 정규화 함수 - 0~1 범위로 변환\n const normalizeRadiation = (radiation: number) => {\n if (maxRadiation === minRadiation) return 0.5\n return (radiation - minRadiation) / (maxRadiation - minRadiation)\n }\n\n // 개선된 태양광 보간 함수 - IDW(Inverse Distance Weighting) 기반 보간법\n const interpolateRadiation = (lon: number, lat: number) => {\n // 가장 가까운 이웃 개수 제한 - 모든 포인트 대신 가까운 포인트만 사용하여 국지적 패턴 유지\n const MAX_NEIGHBORS = 5\n\n // 현재 위치에서 모든 포인트까지의 거리 계산\n const distances = points.map(point => {\n const dx = lon - point.lon\n const dy = lat - point.lat\n return {\n point,\n dist: Math.sqrt(dx * dx + dy * dy)\n }\n })\n\n // 거리에 따라 정렬하고 가장 가까운 이웃만 선택\n const nearestNeighbors = distances.sort((a, b) => a.dist - b.dist).slice(0, MAX_NEIGHBORS)\n\n // 포인트가 정확히 일치하는 경우\n if (nearestNeighbors.length > 0 && nearestNeighbors[0].dist < 0.0001) {\n return nearestNeighbors[0].point.effectiveRadiation\n }\n\n // 역거리 가중치 보간법 적용 - 거리의 제곱에 반비례하는 가중치 사용\n let totalWeight = 0\n let weightedRadiation = 0\n\n // 가중치 지수 - 낮을수록 더 부드러운 보간, 높을수록 가까운 값에 더 큰 영향\n const power = 1.5\n\n nearestNeighbors.forEach(({ point, dist }) => {\n // 작은 값으로 나누기 방지\n const adjustedDist = Math.max(dist, 0.0001)\n const weight = 1 / Math.pow(adjustedDist, power)\n\n totalWeight += weight\n weightedRadiation += point.effectiveRadiation * weight\n })\n\n return weightedRadiation / totalWeight\n }\n\n // 태양광에 따른 색상 계산 함수\n const getColorFromRadiation = (radiation: number) => {\n const normalized = normalizeRadiation(radiation)\n\n // 태양광 표현에 적합한 색상 - 낮은 값은 더 어두운 노랑, 높은 값은 밝고 강렬한 노랑\n const hue = 50 - normalized * 10 // 50~40 범위의 노란색\n const saturation = 60 + normalized * 40 // 60~100% 채도\n const lightness = 30 + normalized * 60 // 30~90% 밝기\n\n // 태양광 강도에 따라 투명도 조정 - 강한 태양광은 더 선명하게\n const alpha = 0.3 + normalized * 0.5\n\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`\n }\n\n // 그라데이션을 적용할 그리드 생성\n const bounds = {\n minLon: Infinity,\n maxLon: -Infinity,\n minLat: Infinity,\n maxLat: -Infinity\n }\n\n // 데이터 포인트로부터 영역 경계 계산\n points.forEach(point => {\n bounds.minLon = Math.min(bounds.minLon, point.lon)\n bounds.maxLon = Math.max(bounds.maxLon, point.lon)\n bounds.minLat = Math.min(bounds.minLat, point.lat)\n bounds.maxLat = Math.max(bounds.maxLat, point.lat)\n })\n\n // 경계에 마진 추가\n const margin = 0.1\n bounds.minLon -= margin\n bounds.maxLon += margin\n bounds.minLat -= margin\n bounds.maxLat += margin\n\n // 고해상도 그리드 생성 (더 매끄러운 그라데이션을 위해)\n const gridResolution = 100 // 적절한 해상도로 조정\n const lonStep = (bounds.maxLon - bounds.minLon) / gridResolution\n const latStep = (bounds.maxLat - bounds.minLat) / gridResolution\n\n // 인접 셀 간의 값 차이 최소화를 위한 스무딩 함수\n const smoothGrid = (grid: number[][], passes: number = 2): number[][] => {\n const rows = grid.length\n if (rows === 0) return grid\n\n const cols = grid[0].length\n const result = Array(rows)\n .fill(0)\n .map(() => Array(cols).fill(0))\n\n // 여러 번 스무딩 패스 적용\n for (let pass = 0; pass < passes; pass++) {\n const currentGrid = pass === 0 ? grid : result\n\n for (let i = 0; i < rows; i++) {\n for (let j = 0; j < cols; j++) {\n // 현재 셀과 인접 셀의 평균 계산\n let sum = currentGrid[i][j]\n let count = 1\n\n // 상하좌우 셀 확인\n const neighbors = [\n [i - 1, j],\n [i + 1, j],\n [i, j - 1],\n [i, j + 1]\n ]\n\n neighbors.forEach(([ni, nj]) => {\n if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) {\n sum += currentGrid[ni][nj]\n count++\n }\n })\n\n // 평균값으로 스무딩 (약간의 원본 가중치 부여)\n result[i][j] = currentGrid[i][j] * 0.6 + ((sum - currentGrid[i][j]) / (count - 1)) * 0.4\n }\n }\n }\n\n return result\n }\n\n // 태양광 데이터 그리드 생성\n const radiationGrid: number[][] = []\n\n for (let i = 0; i < gridResolution; i++) {\n radiationGrid[i] = []\n for (let j = 0; j < gridResolution; j++) {\n const centerLon = bounds.minLon + (j + 0.5) * lonStep\n const centerLat = bounds.minLat + (i + 0.5) * latStep\n radiationGrid[i][j] = interpolateRadiation(centerLon, centerLat)\n }\n }\n\n // 그리드 스무딩 적용\n const smoothedGrid = smoothGrid(radiationGrid, 2)\n\n // 셀 생성 및 피처 추가\n for (let i = 0; i < gridResolution; i++) {\n for (let j = 0; j < gridResolution; j++) {\n const lonMin = bounds.minLon + j * lonStep\n const lonMax = bounds.minLon + (j + 1) * lonStep\n const latMin = bounds.minLat + i * latStep\n const latMax = bounds.minLat + (i + 1) * latStep\n\n // 스무딩된 값 사용\n const interpolatedRadiation = smoothedGrid[i][j]\n\n // 너무 작은 태양광은 제외 (최소값의 5% 미만)\n if (interpolatedRadiation < minRadiation + (maxRadiation - minRadiation) * 0.05) continue\n\n // 셀 좌표 계산 및 변환\n const coordinates = [\n [lonMin, latMin],\n [lonMax, latMin],\n [lonMax, latMax],\n [lonMin, latMax],\n [lonMin, latMin]\n ].map(coord => fromLonLat(coord))\n\n // 다각형 피처 생성\n const feature = new Feature({\n geometry: new Polygon([coordinates]),\n radiation: interpolatedRadiation\n })\n\n source.addFeature(feature)\n }\n }\n\n // CSS 블렌딩 모드 정의를 위한 클래스 이름 추가\n const layerClass = 'solar-layer'\n\n // 블렌딩 모드 스타일 추가\n if (!document.getElementById('solar-layer-style')) {\n const style = document.createElement('style')\n style.id = 'solar-layer-style'\n style.textContent = `\n .${layerClass} canvas {\n mix-blend-mode: overlay;\n }\n `\n document.head.appendChild(style)\n }\n\n // 벡터 레이어 생성\n return new VectorLayer({\n source,\n zIndex: 8,\n className: layerClass,\n opacity: 0.8,\n style: feature => {\n const radiation = feature.get('radiation') as number\n return new Style({\n fill: new Fill({\n color: getColorFromRadiation(radiation)\n }),\n // 경계선 완전히 제거\n stroke: undefined\n })\n }\n })\n }\n\n /**\n * 레이어에 날씨 데이터를 설정합니다.\n */\n setWeatherData(data: WeatherData[]) {\n this.weatherData = data\n this.updateLayer()\n }\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import BaseLayer from 'ol/layer/Base';
|
|
2
|
+
import { BaseWeatherLayer } from './base-weather-layer.js';
|
|
3
|
+
import { WeatherLayerType } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* 온도 레이어 구현
|
|
6
|
+
*/
|
|
7
|
+
export declare class TemperatureLayer extends BaseWeatherLayer {
|
|
8
|
+
getType(): WeatherLayerType;
|
|
9
|
+
/**
|
|
10
|
+
* 히트맵 레이어를 생성하여 온도를 표현합니다.
|
|
11
|
+
*/
|
|
12
|
+
createLayer(): BaseLayer;
|
|
13
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import VectorSource from 'ol/source/Vector';
|
|
2
|
+
import Feature from 'ol/Feature';
|
|
3
|
+
import { fromLonLat } from 'ol/proj';
|
|
4
|
+
import Polygon from 'ol/geom/Polygon';
|
|
5
|
+
import { Fill } from 'ol/style';
|
|
6
|
+
import VectorLayer from 'ol/layer/Vector';
|
|
7
|
+
import { Style } from 'ol/style';
|
|
8
|
+
import { BaseWeatherLayer } from './base-weather-layer.js';
|
|
9
|
+
/**
|
|
10
|
+
* 온도 레이어 구현
|
|
11
|
+
*/
|
|
12
|
+
export class TemperatureLayer extends BaseWeatherLayer {
|
|
13
|
+
getType() {
|
|
14
|
+
return 'temperature';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 히트맵 레이어를 생성하여 온도를 표현합니다.
|
|
18
|
+
*/
|
|
19
|
+
createLayer() {
|
|
20
|
+
const source = new VectorSource();
|
|
21
|
+
console.log('온도 레이어 생성 - 데이터 존재 여부:', !!this.weatherData, '데이터 개수:', this.weatherData?.length);
|
|
22
|
+
if (!this.weatherData || this.weatherData.length === 0) {
|
|
23
|
+
console.log('온도 레이어: 날씨 데이터가 없음');
|
|
24
|
+
return new VectorLayer({
|
|
25
|
+
source,
|
|
26
|
+
zIndex: 5
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// 온도 값이 있는지 확인
|
|
30
|
+
const validData = this.weatherData.filter(data => {
|
|
31
|
+
const hasTemp = data.temperature && typeof data.temperature.current === 'number';
|
|
32
|
+
if (!hasTemp) {
|
|
33
|
+
console.log('온도 데이터 없음:', data);
|
|
34
|
+
}
|
|
35
|
+
return hasTemp;
|
|
36
|
+
});
|
|
37
|
+
console.log('유효한 온도 데이터 수:', validData.length);
|
|
38
|
+
if (validData.length === 0) {
|
|
39
|
+
console.log('온도 레이어: 유효한 온도 데이터가 없음');
|
|
40
|
+
return new VectorLayer({
|
|
41
|
+
source,
|
|
42
|
+
zIndex: 5
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// 온도 범위 계산
|
|
46
|
+
let minTemp = Infinity;
|
|
47
|
+
let maxTemp = -Infinity;
|
|
48
|
+
validData.forEach(data => {
|
|
49
|
+
const temp = data.temperature.current;
|
|
50
|
+
minTemp = Math.min(minTemp, temp);
|
|
51
|
+
maxTemp = Math.max(maxTemp, temp);
|
|
52
|
+
});
|
|
53
|
+
// 각 데이터 포인트의 위치와 온도 추출
|
|
54
|
+
const points = validData.map(data => ({
|
|
55
|
+
lon: data.location.longitude,
|
|
56
|
+
lat: data.location.latitude,
|
|
57
|
+
temp: data.temperature.current
|
|
58
|
+
}));
|
|
59
|
+
console.log('온도 데이터 포인트:', points.length, '첫 번째 포인트:', points[0]);
|
|
60
|
+
// 온도 정규화 함수 - 0~1 범위로 변환
|
|
61
|
+
const normalizeTemp = (temp) => {
|
|
62
|
+
if (maxTemp === minTemp)
|
|
63
|
+
return 0.5;
|
|
64
|
+
return (temp - minTemp) / (maxTemp - minTemp);
|
|
65
|
+
};
|
|
66
|
+
// 개선된 온도 보간 함수 - IDW(Inverse Distance Weighting) 기반 보간법
|
|
67
|
+
const interpolateTemp = (lon, lat) => {
|
|
68
|
+
// 가장 가까운 이웃 개수 제한 - 모든 포인트 대신 가까운 포인트만 사용하여 국지적 패턴 유지
|
|
69
|
+
const MAX_NEIGHBORS = 5;
|
|
70
|
+
// 현재 위치에서 모든 포인트까지의 거리 계산
|
|
71
|
+
const distances = points.map(point => {
|
|
72
|
+
const dx = lon - point.lon;
|
|
73
|
+
const dy = lat - point.lat;
|
|
74
|
+
return {
|
|
75
|
+
point,
|
|
76
|
+
dist: Math.sqrt(dx * dx + dy * dy)
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// 거리에 따라 정렬하고 가장 가까운 이웃만 선택
|
|
80
|
+
const nearestNeighbors = distances.sort((a, b) => a.dist - b.dist).slice(0, MAX_NEIGHBORS);
|
|
81
|
+
// 포인트가 정확히 일치하는 경우
|
|
82
|
+
if (nearestNeighbors.length > 0 && nearestNeighbors[0].dist < 0.0001) {
|
|
83
|
+
return nearestNeighbors[0].point.temp;
|
|
84
|
+
}
|
|
85
|
+
// 역거리 가중치 보간법 적용 - 거리의 제곱에 반비례하는 가중치 사용
|
|
86
|
+
let totalWeight = 0;
|
|
87
|
+
let weightedTemp = 0;
|
|
88
|
+
// 가중치 지수 - 낮을수록 더 부드러운 보간, 높을수록 가까운 값에 더 큰 영향
|
|
89
|
+
const power = 1.5;
|
|
90
|
+
nearestNeighbors.forEach(({ point, dist }) => {
|
|
91
|
+
// 작은 값으로 나누기 방지
|
|
92
|
+
const adjustedDist = Math.max(dist, 0.0001);
|
|
93
|
+
const weight = 1 / Math.pow(adjustedDist, power);
|
|
94
|
+
totalWeight += weight;
|
|
95
|
+
weightedTemp += point.temp * weight;
|
|
96
|
+
});
|
|
97
|
+
return weightedTemp / totalWeight;
|
|
98
|
+
};
|
|
99
|
+
// 온도에 따른 색상 계산 함수
|
|
100
|
+
const getColorFromTemp = (temp) => {
|
|
101
|
+
const normalized = normalizeTemp(temp);
|
|
102
|
+
// HSL 색상 모델 사용 - 보다 자연스러운 온도 색상 맵핑
|
|
103
|
+
// 파랑(추움, 240) -> 하양(중간, 180) -> 빨강(더움, 0)
|
|
104
|
+
const hue = Math.max(0, Math.min(240, 240 - normalized * 240));
|
|
105
|
+
// 온도에 따라 채도 변화 - 극단적 온도에서 더 선명하게
|
|
106
|
+
const saturation = 70 + Math.abs(normalized - 0.5) * 60;
|
|
107
|
+
// 온도에 따라 밝기 조정 - 중간 온도는 더 밝게
|
|
108
|
+
const lightness = 50 + Math.abs(normalized - 0.5) * 20;
|
|
109
|
+
// 투명도 설정 - 중간 온도는 더 투명하게, 극단적 온도는 더 선명하게
|
|
110
|
+
const alpha = 0.4 + Math.abs(normalized - 0.5) * 0.4;
|
|
111
|
+
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
|
|
112
|
+
};
|
|
113
|
+
// 그라데이션을 적용할 그리드 생성
|
|
114
|
+
const bounds = {
|
|
115
|
+
minLon: Infinity,
|
|
116
|
+
maxLon: -Infinity,
|
|
117
|
+
minLat: Infinity,
|
|
118
|
+
maxLat: -Infinity
|
|
119
|
+
};
|
|
120
|
+
// 데이터 포인트로부터 영역 경계 계산
|
|
121
|
+
points.forEach(point => {
|
|
122
|
+
bounds.minLon = Math.min(bounds.minLon, point.lon);
|
|
123
|
+
bounds.maxLon = Math.max(bounds.maxLon, point.lon);
|
|
124
|
+
bounds.minLat = Math.min(bounds.minLat, point.lat);
|
|
125
|
+
bounds.maxLat = Math.max(bounds.maxLat, point.lat);
|
|
126
|
+
});
|
|
127
|
+
// 경계에 마진 추가
|
|
128
|
+
const margin = 0.1;
|
|
129
|
+
bounds.minLon -= margin;
|
|
130
|
+
bounds.maxLon += margin;
|
|
131
|
+
bounds.minLat -= margin;
|
|
132
|
+
bounds.maxLat += margin;
|
|
133
|
+
// 고해상도 그리드 생성 (더 매끄러운 그라데이션을 위해)
|
|
134
|
+
const gridResolution = 120; // 적절한 해상도로 조정
|
|
135
|
+
const lonStep = (bounds.maxLon - bounds.minLon) / gridResolution;
|
|
136
|
+
const latStep = (bounds.maxLat - bounds.minLat) / gridResolution;
|
|
137
|
+
// 인접 셀 간의 온도 차이 최소화를 위한 스무딩 함수
|
|
138
|
+
const smoothGrid = (grid, passes = 2) => {
|
|
139
|
+
const rows = grid.length;
|
|
140
|
+
if (rows === 0)
|
|
141
|
+
return grid;
|
|
142
|
+
const cols = grid[0].length;
|
|
143
|
+
const result = Array(rows)
|
|
144
|
+
.fill(0)
|
|
145
|
+
.map(() => Array(cols).fill(0));
|
|
146
|
+
// 여러 번 스무딩 패스 적용
|
|
147
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
148
|
+
const currentGrid = pass === 0 ? grid : result;
|
|
149
|
+
for (let i = 0; i < rows; i++) {
|
|
150
|
+
for (let j = 0; j < cols; j++) {
|
|
151
|
+
// 현재 셀과 인접 셀의 평균 계산
|
|
152
|
+
let sum = currentGrid[i][j];
|
|
153
|
+
let count = 1;
|
|
154
|
+
// 상하좌우 셀 확인
|
|
155
|
+
const neighbors = [
|
|
156
|
+
[i - 1, j],
|
|
157
|
+
[i + 1, j],
|
|
158
|
+
[i, j - 1],
|
|
159
|
+
[i, j + 1]
|
|
160
|
+
];
|
|
161
|
+
neighbors.forEach(([ni, nj]) => {
|
|
162
|
+
if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) {
|
|
163
|
+
sum += currentGrid[ni][nj];
|
|
164
|
+
count++;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// 평균값으로 스무딩 (약간의 원본 가중치 부여)
|
|
168
|
+
result[i][j] = currentGrid[i][j] * 0.6 + ((sum - currentGrid[i][j]) / (count - 1)) * 0.4;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
};
|
|
174
|
+
// 온도 데이터 그리드 생성
|
|
175
|
+
const tempGrid = [];
|
|
176
|
+
for (let i = 0; i < gridResolution; i++) {
|
|
177
|
+
tempGrid[i] = [];
|
|
178
|
+
for (let j = 0; j < gridResolution; j++) {
|
|
179
|
+
const centerLon = bounds.minLon + (j + 0.5) * lonStep;
|
|
180
|
+
const centerLat = bounds.minLat + (i + 0.5) * latStep;
|
|
181
|
+
tempGrid[i][j] = interpolateTemp(centerLon, centerLat);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// 그리드 스무딩 적용
|
|
185
|
+
const smoothedGrid = smoothGrid(tempGrid, 2);
|
|
186
|
+
// 셀 생성 및 피처 추가
|
|
187
|
+
for (let i = 0; i < gridResolution; i++) {
|
|
188
|
+
for (let j = 0; j < gridResolution; j++) {
|
|
189
|
+
const lonMin = bounds.minLon + j * lonStep;
|
|
190
|
+
const lonMax = bounds.minLon + (j + 1) * lonStep;
|
|
191
|
+
const latMin = bounds.minLat + i * latStep;
|
|
192
|
+
const latMax = bounds.minLat + (i + 1) * latStep;
|
|
193
|
+
// 스무딩된 온도값 사용
|
|
194
|
+
const interpolatedTemp = smoothedGrid[i][j];
|
|
195
|
+
// 셀 좌표 계산 및 변환
|
|
196
|
+
const coordinates = [
|
|
197
|
+
[lonMin, latMin],
|
|
198
|
+
[lonMax, latMin],
|
|
199
|
+
[lonMax, latMax],
|
|
200
|
+
[lonMin, latMax],
|
|
201
|
+
[lonMin, latMin]
|
|
202
|
+
].map(coord => fromLonLat(coord));
|
|
203
|
+
// 다각형 피처 생성
|
|
204
|
+
const feature = new Feature({
|
|
205
|
+
geometry: new Polygon([coordinates]),
|
|
206
|
+
temperature: interpolatedTemp
|
|
207
|
+
});
|
|
208
|
+
source.addFeature(feature);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// 벡터 레이어 생성 - 경계선 제거
|
|
212
|
+
return new VectorLayer({
|
|
213
|
+
source,
|
|
214
|
+
zIndex: 6,
|
|
215
|
+
opacity: 0.8, // 전체 레이어 투명도 설정
|
|
216
|
+
className: 'temperature-layer', // 사용자 정의 클래스 이름 추가
|
|
217
|
+
style: feature => {
|
|
218
|
+
const temp = feature.get('temperature');
|
|
219
|
+
return new Style({
|
|
220
|
+
fill: new Fill({
|
|
221
|
+
color: getColorFromTemp(temp)
|
|
222
|
+
}),
|
|
223
|
+
// 경계선 완전히 제거
|
|
224
|
+
stroke: undefined
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=temperature-layer.js.map
|