parameter-mapping.service.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. import { Injectable } from '@angular/core';
  2. import { Observable, of } from 'rxjs';
  3. import {
  4. ColorMappingParams,
  5. SpaceMappingParams,
  6. MaterialMappingParams,
  7. ColorMapping,
  8. SpaceZone,
  9. MaterialMapping
  10. } from '../models/requirement-mapping.interface';
  11. @Injectable({
  12. providedIn: 'root'
  13. })
  14. export class ParameterMappingService {
  15. constructor() {}
  16. /**
  17. * 映射颜色参数
  18. * @param analysisResult 分析结果
  19. */
  20. mapColorParameters(analysisResult: any): Observable<ColorMappingParams> {
  21. try {
  22. const colorParams: ColorMappingParams = {
  23. primaryColors: this.generateColorMappings(analysisResult),
  24. colorHarmony: this.determineColorHarmony(analysisResult),
  25. saturation: this.calculateSaturationLevel(analysisResult),
  26. brightness: this.calculateBrightnessLevel(analysisResult),
  27. contrast: 50,
  28. temperature: 'neutral'
  29. };
  30. return of(colorParams);
  31. } catch (error) {
  32. return of(this.getDefaultColorParams());
  33. }
  34. }
  35. /**
  36. * 映射空间参数
  37. * @param analysisResult 分析结果
  38. */
  39. mapSpaceParameters(analysisResult: any): Observable<SpaceMappingParams> {
  40. try {
  41. const spaceParams: SpaceMappingParams = {
  42. dimensions: { width: 400, height: 280, depth: 400, unit: 'meter' },
  43. layout: {
  44. type: this.determineLayout(analysisResult) as any,
  45. flow: 'linear',
  46. zones: this.identifySpaceZones(analysisResult)
  47. },
  48. scale: {
  49. furniture: 70,
  50. ceiling: 80,
  51. openness: 60
  52. }
  53. };
  54. return of(spaceParams);
  55. } catch (error) {
  56. return of(this.getDefaultSpaceParams());
  57. }
  58. }
  59. /**
  60. * 映射材质参数
  61. * @param analysisResult 分析结果
  62. */
  63. mapMaterialParameters(analysisResult: any): Observable<MaterialMappingParams> {
  64. try {
  65. const materialParams: MaterialMappingParams = {
  66. surfaceMaterials: this.generateMaterialMappings(analysisResult),
  67. textureScale: 50,
  68. reflectivity: 30,
  69. roughness: 50,
  70. metallic: 10
  71. };
  72. return of(materialParams);
  73. } catch (error) {
  74. return of(this.getDefaultMaterialParams());
  75. }
  76. }
  77. /**
  78. * 综合映射所有参数
  79. * @param analysisResult 分析结果
  80. */
  81. mapAllParameters(analysisResult: any): Observable<{
  82. colorParams: ColorMappingParams;
  83. spaceParams: SpaceMappingParams;
  84. materialParams: MaterialMappingParams;
  85. }> {
  86. try {
  87. const colorParams = this.mapColorParameters(analysisResult);
  88. const spaceParams = this.mapSpaceParameters(analysisResult);
  89. const materialParams = this.mapMaterialParameters(analysisResult);
  90. return new Observable(observer => {
  91. Promise.all([
  92. colorParams.toPromise(),
  93. spaceParams.toPromise(),
  94. materialParams.toPromise()
  95. ]).then(([color, space, material]) => {
  96. observer.next({
  97. colorParams: color!,
  98. spaceParams: space!,
  99. materialParams: material!
  100. });
  101. observer.complete();
  102. }).catch(error => {
  103. observer.error(error);
  104. });
  105. });
  106. } catch (error) {
  107. return of({
  108. colorParams: this.getDefaultColorParams(),
  109. spaceParams: this.getDefaultSpaceParams(),
  110. materialParams: this.getDefaultMaterialParams()
  111. });
  112. }
  113. }
  114. // ==================== 颜色参数映射方法 ====================
  115. /**
  116. * 提取主要颜色
  117. */
  118. private extractPrimaryColors(analysisResult: any): string[] {
  119. if (analysisResult.enhancedColorAnalysis?.colorWheel?.dominantColors) {
  120. return analysisResult.enhancedColorAnalysis.colorWheel.dominantColors
  121. .slice(0, 5)
  122. .map((color: any) => color.hex || color.name || '#FFFFFF');
  123. }
  124. if (analysisResult.enhancedColorAnalysis?.colorPalette?.primaryColors) {
  125. return analysisResult.enhancedColorAnalysis.colorPalette.primaryColors
  126. .slice(0, 5)
  127. .map((color: any) => color.hex || color.value || '#FFFFFF');
  128. }
  129. return ['#FFFFFF', '#F5F5F5', '#E0E0E0']; // 默认中性色
  130. }
  131. /**
  132. * 确定色彩和谐度
  133. */
  134. private determineColorHarmony(analysisResult: any): 'monochromatic' | 'analogous' | 'complementary' | 'triadic' | 'split-complementary' {
  135. if (analysisResult.enhancedColorAnalysis?.colorHarmony?.harmonyType) {
  136. return analysisResult.enhancedColorAnalysis.colorHarmony.harmonyType;
  137. }
  138. // 基于色彩分布分析和谐度
  139. const colors = this.extractPrimaryColors(analysisResult);
  140. if (colors.length <= 2) {
  141. return 'monochromatic';
  142. } else if (colors.length === 3) {
  143. return 'triadic';
  144. } else {
  145. return 'analogous';
  146. }
  147. }
  148. /**
  149. * 计算饱和度级别
  150. */
  151. private calculateSaturationLevel(analysisResult: any): number {
  152. if (analysisResult.enhancedColorAnalysis?.colorWheel?.saturation) {
  153. return analysisResult.enhancedColorAnalysis.colorWheel.saturation;
  154. }
  155. if (analysisResult.enhancedColorAnalysis?.colorPalette?.averageSaturation) {
  156. return analysisResult.enhancedColorAnalysis.colorPalette.averageSaturation;
  157. }
  158. return 50; // 默认中等饱和度
  159. }
  160. /**
  161. * 计算亮度级别
  162. */
  163. private calculateBrightnessLevel(analysisResult: any): number {
  164. if (analysisResult.lightingAnalysis?.illuminationAnalysis?.brightness?.overall) {
  165. return analysisResult.lightingAnalysis.illuminationAnalysis.brightness.overall;
  166. }
  167. if (analysisResult.enhancedColorAnalysis?.colorWheel?.brightness) {
  168. return analysisResult.enhancedColorAnalysis.colorWheel.brightness;
  169. }
  170. return 60; // 默认中等亮度
  171. }
  172. /**
  173. * 生成颜色映射
  174. */
  175. private generateColorMappings(analysisResult: any): ColorMapping[] {
  176. const primaryColors = this.extractPrimaryColors(analysisResult);
  177. const mappings: ColorMapping[] = [];
  178. primaryColors.forEach((originalColor, index) => {
  179. mappings.push({
  180. originalColor: originalColor,
  181. mappedColor: this.optimizeColorForSpace(originalColor, analysisResult),
  182. usage: this.determineColorUsage(index),
  183. weight: this.calculateColorCoverage(index, primaryColors.length)
  184. });
  185. });
  186. return mappings;
  187. }
  188. /**
  189. * 优化空间色彩
  190. */
  191. private optimizeColorForSpace(color: string, analysisResult: any): string {
  192. // 根据空间类型和光照条件优化颜色
  193. // 这里实现简化的色彩优化逻辑
  194. return color; // 暂时返回原色彩
  195. }
  196. /**
  197. * 确定颜色用途
  198. */
  199. private determineColorUsage(index: number): 'primary' | 'secondary' | 'accent' | 'background' {
  200. switch (index) {
  201. case 0: return 'primary';
  202. case 1: return 'secondary';
  203. case 2: return 'accent';
  204. default: return 'background';
  205. }
  206. }
  207. /**
  208. * 计算颜色覆盖率
  209. */
  210. private calculateColorCoverage(index: number, totalColors: number): number {
  211. const baseCoverage = [40, 30, 20, 10]; // 主色、次色、强调色、中性色的基础覆盖率
  212. return baseCoverage[index] || (100 - 90) / (totalColors - 3);
  213. }
  214. // ==================== 空间参数映射方法 ====================
  215. /**
  216. * 确定房间类型
  217. */
  218. private determineRoomType(analysisResult: any): string {
  219. // 基于分析结果推断房间类型
  220. if (analysisResult.formAnalysis?.objectRecognition) {
  221. const objects = analysisResult.formAnalysis.objectRecognition.identifiedObjects || [];
  222. // 检查特征物品来判断房间类型
  223. if (objects.some((obj: any) => ['bed', 'pillow', 'nightstand'].includes(obj.category))) {
  224. return 'bedroom';
  225. }
  226. if (objects.some((obj: any) => ['sofa', 'coffee table', 'tv'].includes(obj.category))) {
  227. return 'living_room';
  228. }
  229. if (objects.some((obj: any) => ['stove', 'refrigerator', 'sink'].includes(obj.category))) {
  230. return 'kitchen';
  231. }
  232. if (objects.some((obj: any) => ['toilet', 'shower', 'bathtub'].includes(obj.category))) {
  233. return 'bathroom';
  234. }
  235. }
  236. return 'general'; // 默认通用空间
  237. }
  238. /**
  239. * 估算空间尺寸
  240. */
  241. private estimateDimensions(analysisResult: any): { width: number; height: number; depth: number } {
  242. // 基于透视分析和物体比例估算尺寸
  243. if (analysisResult.formAnalysis?.perspectiveAnalysis) {
  244. const perspective = analysisResult.formAnalysis.perspectiveAnalysis;
  245. return {
  246. width: perspective.estimatedWidth || 400,
  247. height: perspective.estimatedHeight || 280,
  248. depth: perspective.estimatedDepth || 400
  249. };
  250. }
  251. // 默认中等尺寸房间 (单位: cm)
  252. return { width: 400, height: 280, depth: 400 };
  253. }
  254. /**
  255. * 确定布局类型
  256. */
  257. private determineLayout(analysisResult: any): 'open' | 'closed' | 'semi-open' | 'linear' | 'L-shaped' | 'U-shaped' {
  258. if (analysisResult.formAnalysis?.spaceAnalysis?.layoutType) {
  259. return analysisResult.formAnalysis.spaceAnalysis.layoutType;
  260. }
  261. // 基于物体分布分析布局
  262. const roomType = this.determineRoomType(analysisResult);
  263. switch (roomType) {
  264. case 'living_room': return 'open';
  265. case 'kitchen': return 'L-shaped';
  266. case 'bedroom': return 'closed';
  267. case 'bathroom': return 'linear';
  268. default: return 'open';
  269. }
  270. }
  271. /**
  272. * 识别空间区域
  273. */
  274. private identifySpaceZones(analysisResult: any): SpaceZone[] {
  275. const zones: SpaceZone[] = [];
  276. const roomType = this.determineRoomType(analysisResult);
  277. // 根据房间类型定义功能区域
  278. switch (roomType) {
  279. case 'living_room':
  280. zones.push(
  281. { name: '会客区', function: 'seating', area: 40, position: 'center' },
  282. { name: '娱乐区', function: 'entertainment', area: 25, position: 'corner' },
  283. { name: '通道区', function: 'circulation', area: 35, position: 'entrance' }
  284. );
  285. break;
  286. case 'bedroom':
  287. zones.push(
  288. { name: '睡眠区', function: 'sleeping', area: 50, position: 'center' },
  289. { name: '储物区', function: 'storage', area: 30, position: 'wall' },
  290. { name: '梳妆区', function: 'dressing', area: 20, position: 'window' }
  291. );
  292. break;
  293. case 'kitchen':
  294. zones.push(
  295. { name: '烹饪区', function: 'cooking', area: 40, position: 'wall' },
  296. { name: '清洗区', function: 'cleaning', area: 25, position: 'wall' },
  297. { name: '储存区', function: 'storage', area: 35, position: 'corner' }
  298. );
  299. break;
  300. default:
  301. zones.push(
  302. { name: '主要区域', function: 'primary', area: 70, position: 'center' },
  303. { name: '辅助区域', function: 'secondary', area: 30, position: 'corner' }
  304. );
  305. }
  306. return zones;
  307. }
  308. /**
  309. * 分析流线模式
  310. */
  311. private analyzeFlowPattern(analysisResult: any): 'linear' | 'circular' | 'radial' | 'grid' | 'organic' {
  312. const layout = this.determineLayout(analysisResult);
  313. // 根据布局类型推断流线模式
  314. switch (layout) {
  315. case 'linear': return 'linear';
  316. case 'L-shaped':
  317. case 'U-shaped': return 'circular';
  318. case 'open': return 'radial';
  319. default: return 'organic';
  320. }
  321. }
  322. // ==================== 材质参数映射方法 ====================
  323. /**
  324. * 提取主要材质
  325. */
  326. private extractPrimaryMaterials(analysisResult: any): string[] {
  327. if (analysisResult.textureAnalysis?.materialClassification) {
  328. const materials = [];
  329. const classification = analysisResult.textureAnalysis.materialClassification;
  330. if (classification.primaryMaterial) {
  331. materials.push(classification.primaryMaterial.category);
  332. }
  333. if (classification.secondaryMaterials) {
  334. materials.push(...classification.secondaryMaterials.map((m: any) => m.category));
  335. }
  336. return materials.slice(0, 5);
  337. }
  338. return ['wood', 'fabric', 'metal']; // 默认材质组合
  339. }
  340. /**
  341. * 计算纹理复杂度
  342. */
  343. private calculateTextureComplexity(analysisResult: any): 'low' | 'medium' | 'high' {
  344. if (analysisResult.patternAnalysis?.patternRecognition) {
  345. const patternCount = analysisResult.patternAnalysis.patternRecognition.primaryPatterns?.length || 0;
  346. if (patternCount > 3) return 'high';
  347. if (patternCount > 1) return 'medium';
  348. return 'low';
  349. }
  350. if (analysisResult.textureAnalysis?.surfaceProperties?.roughness) {
  351. const roughness = analysisResult.textureAnalysis.surfaceProperties.roughness.level;
  352. if (roughness === 'high') return 'high';
  353. if (roughness === 'medium') return 'medium';
  354. return 'low';
  355. }
  356. return 'medium';
  357. }
  358. /**
  359. * 确定表面处理
  360. */
  361. private determineSurfaceFinish(analysisResult: any): 'matte' | 'satin' | 'semi-gloss' | 'gloss' | 'textured' {
  362. if (analysisResult.textureAnalysis?.surfaceProperties?.reflectivity) {
  363. const reflectivity = analysisResult.textureAnalysis.surfaceProperties.reflectivity.level;
  364. switch (reflectivity) {
  365. case 'high': return 'gloss';
  366. case 'medium': return 'semi-gloss';
  367. case 'low': return 'matte';
  368. default: return 'satin';
  369. }
  370. }
  371. return 'satin'; // 默认缎面处理
  372. }
  373. /**
  374. * 生成材质映射
  375. */
  376. private generateMaterialMappings(analysisResult: any): MaterialMapping[] {
  377. const primaryMaterials = this.extractPrimaryMaterials(analysisResult);
  378. const mappings: MaterialMapping[] = [];
  379. primaryMaterials.forEach((originalMaterial, index) => {
  380. mappings.push({
  381. category: originalMaterial as any,
  382. subtype: originalMaterial,
  383. finish: 'satin',
  384. color: '#8B4513',
  385. coverage: this.calculateMaterialCoverage(index, primaryMaterials.length),
  386. priority: this.determineMaterialUsage(index) as any
  387. });
  388. });
  389. return mappings;
  390. }
  391. /**
  392. * 优化空间材质
  393. */
  394. private optimizeMaterialForSpace(material: string, analysisResult: any): string {
  395. // 根据空间类型和使用需求优化材质选择
  396. const roomType = this.determineRoomType(analysisResult);
  397. // 材质优化映射表
  398. const optimizationMap: {[key: string]: {[key: string]: string}} = {
  399. 'bathroom': {
  400. 'wood': 'ceramic',
  401. 'fabric': 'vinyl',
  402. 'paper': 'ceramic'
  403. },
  404. 'kitchen': {
  405. 'fabric': 'quartz',
  406. 'paper': 'ceramic',
  407. 'wood': 'laminate'
  408. }
  409. };
  410. return optimizationMap[roomType]?.[material] || material;
  411. }
  412. /**
  413. * 获取材质属性
  414. */
  415. private getMaterialProperties(material: string): {
  416. durability: number;
  417. maintenance: 'low' | 'medium' | 'high';
  418. cost: 'low' | 'medium' | 'high';
  419. sustainability: number;
  420. } {
  421. const propertiesMap: {[key: string]: any} = {
  422. 'wood': { durability: 75, maintenance: 'medium', cost: 'medium', sustainability: 85 },
  423. 'metal': { durability: 90, maintenance: 'low', cost: 'high', sustainability: 70 },
  424. 'fabric': { durability: 60, maintenance: 'high', cost: 'low', sustainability: 60 },
  425. 'ceramic': { durability: 85, maintenance: 'low', cost: 'medium', sustainability: 80 },
  426. 'glass': { durability: 70, maintenance: 'medium', cost: 'medium', sustainability: 90 },
  427. 'stone': { durability: 95, maintenance: 'low', cost: 'high', sustainability: 95 }
  428. };
  429. return propertiesMap[material] || { durability: 70, maintenance: 'medium', cost: 'medium', sustainability: 70 };
  430. }
  431. /**
  432. * 确定材质用途
  433. */
  434. private determineMaterialUsage(index: number): 'primary' | 'secondary' | 'accent' | 'functional' {
  435. switch (index) {
  436. case 0: return 'primary';
  437. case 1: return 'secondary';
  438. case 2: return 'accent';
  439. default: return 'functional';
  440. }
  441. }
  442. /**
  443. * 计算材质覆盖率
  444. */
  445. private calculateMaterialCoverage(index: number, totalMaterials: number): number {
  446. const baseCoverage = [50, 30, 15, 5]; // 主材质、次材质、强调材质、功能材质的基础覆盖率
  447. return baseCoverage[index] || (100 - 95) / (totalMaterials - 3);
  448. }
  449. /**
  450. * 评估耐久性要求
  451. */
  452. private assessDurabilityRequirements(analysisResult: any): 'low' | 'medium' | 'high' {
  453. const roomType = this.determineRoomType(analysisResult);
  454. // 根据空间类型确定耐久性要求
  455. switch (roomType) {
  456. case 'kitchen':
  457. case 'bathroom': return 'high';
  458. case 'living_room': return 'medium';
  459. case 'bedroom': return 'low';
  460. default: return 'medium';
  461. }
  462. }
  463. // ==================== 默认参数方法 ====================
  464. private getDefaultColorParams(): ColorMappingParams {
  465. return {
  466. primaryColors: [
  467. { originalColor: '#FFFFFF', mappedColor: '#FFFFFF', weight: 40, usage: 'primary' },
  468. { originalColor: '#F5F5F5', mappedColor: '#F5F5F5', weight: 30, usage: 'secondary' },
  469. { originalColor: '#E0E0E0', mappedColor: '#E0E0E0', weight: 30, usage: 'background' }
  470. ],
  471. colorHarmony: 'monochromatic',
  472. saturation: 50,
  473. brightness: 70,
  474. contrast: 50,
  475. temperature: 'neutral'
  476. };
  477. }
  478. private getDefaultSpaceParams(): SpaceMappingParams {
  479. return {
  480. dimensions: { width: 400, height: 280, depth: 400, unit: 'meter' },
  481. layout: {
  482. type: 'open',
  483. flow: 'linear',
  484. zones: [
  485. {
  486. name: '主要区域',
  487. position: 'center',
  488. area: 70,
  489. function: '主要活动区域'
  490. },
  491. {
  492. name: '辅助区域',
  493. position: 'corner',
  494. area: 30,
  495. function: '辅助功能区域'
  496. }
  497. ]
  498. },
  499. scale: {
  500. furniture: 1.0,
  501. ceiling: 2.8,
  502. openness: 0.7
  503. }
  504. };
  505. }
  506. private getDefaultMaterialParams(): MaterialMappingParams {
  507. return {
  508. surfaceMaterials: [],
  509. textureScale: 50,
  510. reflectivity: 30,
  511. roughness: 50,
  512. metallic: 10
  513. };
  514. }
  515. }