由于篇幅有限,下篇的面试技术攻克篇只能够展示出部分的面试题,详细完整版以及答案解析,有需要的可以关注
public class LatLngVO {
private Double lat;
private Double lng;
}
/**
* @param vo 重心
* @param voList 多边形经纬度集合
*/
private static void calculateCenter(LatLngVO vo, List<LatLngVO> voList) {
double pointX = 0.0;
double pointY = 0.0;
double totalArea = 0.0;
LatLngVO p1 = voList.get(1);
for (int i = 2; i < voList.size(); i++) {
LatLngVO p2 = voList.get(i);
double area = calculateArea(voList.get(0), p1, p2);
totalArea += area;
pointX += (voList.get(0).getLng() + p1.getLng() + p2.getLng()) * area;
pointY += (voList.get(0).getLat() + p1.getLat() + p2.getLat()) * area;
p1 = p2;
}
if (totalArea != 0.0) {
vo.setLng(pointX / totalArea / 3);
vo.setLat(pointY / totalArea / 3);
}
}
private static double calculateArea(LatLngVO p0, LatLngVO p1, LatLngVO p2) {
double area = p0.getLng() * p1.getLat()
+ p1.getLng() * p2.getLat()
+ p2.getLng() * p0.getLat()
- p1.getLng() * p0.getLat()
- p2.getLng() * p1.getLat()
- p0.getLng() * p2.getLat();
return area / 2 ;
}
在附上所有代码之前。我对当时所实现的产品功能和业务场景做大致讲解:
业务场景:
1、有项目(project)、物业(buinding)、单元(unit)三个核心实体,均为一对多关系
2、在开发3d之前。已有2维描边基础,所以有现成的经纬度(建筑群的墙角)
产品功能:
1、按照录入的楼层层高、数量、是否在租等信息展示模型
2、点击模型时,改变颜色或者显示相关弹窗等
重点:
1、构建模型时可能存在楼层悬空后,楼层之间区分不明显,此时可以在中间塞入一个高度接近0且颜色较深的模型模拟出地板的效果
2、模型的点击事件不是很准确(这是必然的,和角度也有一点关系)
代码主要是前端js、vue代码,仅供参考,若有不明确之处,还望多多实验,或评论留言,
附上的代码中 我去掉了一些无关代码,若用心阅读,应该是能理解大致含义的,主要关注方法initAMap即可
<template>
<div class="box">
<div id="map-3d"></div>
</div>
</template>
<script>
import Vue from 'vue';
import AMap from 'AMap';
import AMapUI from 'AMapUI';
import mapUtil from '../../../utils/mapUtils';
import emptyUtil from '../../../utils/emptyUtils'
import $ from 'jquery';
Vue.prototype.$message = Message;
let mapType = {
weixing : '#map-3d > div.amap-ui-control-container.amap-ui-retina.amap-ui-control-position-rt.amap-ui-control-theme-light > div > form > div.amap-ui-control-layer-base > div.amap-ui-control-layer-base-item.amap-ui-control-layer-base-item-satellite > label',
standard : '#map-3d > div.amap-ui-control-container.amap-ui-retina.amap-ui-control-position-rt.amap-ui-control-theme-light > div > form > div.amap-ui-control-layer-base > div.amap-ui-control-layer-base-item.amap-ui-control-layer-base-item-tile > label'
};
let map;
let object3Dlayer;
let pool3ds = [];
let texts = [];
let sdh = 31;
let selectColor = [255 / 255, 245 / 255, 47 / 255, 0.9];
let noUnAreaColor = [131 / 255, 131 / 255, 131 / 255, 0.61];
let hasUnAreaColor = [10 / 255, 183 / 255, 168 / 255, 0.66];
let floorLineC = [9 / 255, 0 / 255, 0 / 255, 0.99];
let landLineC = [6 / 255, 221 / 255, 255 / 255, 1];
// let fc = "rgb(6,221,255)";
let landFaceC = [236 / 255, 245 / 255, 255 / 255, 0.35];
export default {
data() {
return {
mapType : 0,
h3d: 30,
position: {
lng: null,
lat: null,
},
enums: {
},
project: {
},
globalVariable: {
buildingId: null,
unitId: null,
building: null,
},
buildingList: [],
unit: {
unitTab: [],
unitActive: '',
floorActive: '',
},
editUnit: {
},
map3d: {
projectId: null,
buildings: [],
lat: null,
lng: null,
landPoints: [],
moveTable: [],
},
visible: {
},
editName: {
id: null,
name: '',
},
center: {
lng: null,
lat: null,
},
loading: {},
rules: {},
copy: {
unitList: [],
floorNumbers: [],
unitId: null,
buildingId: null,
buildingList: [],
},
}
},
components: {
PriceSetting: PriceSetting,
UnitDetail: UnitDetail,
},
mounted() {
this.initAMap();
},
methods: {
echo3dModel() {
this.initAMap();
this.visible.removeModelVisible = false;
},
remove3dModel() {
map.remove(object3Dlayer);
pool3ds = [];
texts.forEach(t => {
t.setMap(null);
});
texts = [];
this.visible.removeModelVisible = true;
},
addOneUnit() {
this.resetUnit();
this.visible.addUnitVisible = true;
dealWithAddUnit(this);
},
cutoverMapType() {
if (this.mapType === 1) {
$(mapType.standard).click();
this.mapType = 0;
return
}
if (this.mapType === 0) {
$(mapType.weixing).click();
this.mapType = 1;
}
},
copyUnitVue() {
if (this.copy.floorNumbers.length === 0) {
this.$message.warning('请选择目标楼层');
return;
}
let buildingId = this.copy.buildingId;
let unitId = this.copy.unitId;
let floorNumber = this.copy.floorNumbers.join();
this.$axios.get(this.$pmsPath + '/cms/rentalUnit/batchCopy.do?originUnitId=' + unitId
+ '&targetBuildingId=' + buildingId + '&floorNumbers=' + floorNumber)
.then(res => {
if (res.code === '0') {
this.visible.copyRentalUnitDialog = false;
this.$message.success('复制成功');
pool3ds = [];
this.initAMap();
let _this = this;
setTimeout(function () {
_this.showUnitTab(floorNumber);
}, 1000);
} else {
this.$message.error('复制失败 :' + res.msg);
}
});
},
changeH3d() {
this.initAMap();
},
resetPosition() {
map.setCenter([this.position.lng, this.position.lat]);
},
unitCutover(tab) {
this.globalVariable.unitId = tab.label.replace('单元#', '');
this.resetUnit();
this.unitInfo();
},
initAMap() {
this.map3d.projectId = this.$route.params.projectId;
this.queryAllBuilding();
this.sidebarInfo.projectId = this.map3d.projectId;
this.$axios.get(this.$pmsPath + '/cms/project/gd3dMap.do?projectId=' + this.map3d.projectId)
.then(res => {
if (res.code === '0') {
let data = res.data;
this.map3d.lat = data.lat;
this.map3d.lng = data.lng;
this.map3d.landPoints = data.landPoints;
this.map3d.buildings = data.buildings;
this.sidebarInfo.parkName = data.parkName;
this.sidebarInfo.buildingIds = data.buildingIds;
this.sidebarInfo.addressDesc = data.addressDesc;
sdh = this.h3d;
draw(this);
} else {
this.map3d.lat = 31.220946;
this.map3d.lng = 121.4181333;
}
});
},
queryAllBuilding() {
this.$axios.get(this.$pmsPath + '/cms/project/buildings3d.do?projectId=' + this.map3d.projectId)
.then(res => {
if (res.code === '0') {
this.buildingList = res.data;
}
});
},
drawBuilding(id, pathList, building) {
this.$axios.get(this.$pmsPath + '/cms/project/centerPoint.do?id=' + id)
.then(res => {
if (res.code === '0') {
let center = res.data;
this.center.lng = center.lng;
this.center.lat = center.lat;
initMesh(pathList, building, this);
}
});
},
}
}
function dealWithAddUnit(_this) {
let building = null;
_this.buildingList.forEach(b => {
if (b.id === _this.globalVariable.buildingId) {
building = b;
}
});
_this.resetUnit();
_this.editUnit.buildingId = _this.globalVariable.buildingId;
_this.globalVariable.unitId = null;
_this.editUnit.warehouseType = building.warehouseType;
}
function draw(_this) {
let curr = mapUtil.bd_google_encrypt(_this.map3d.lat, _this.map3d.lng);
map = new AMap.Map('map-3d', {
viewMode: '3D', // 开启 3D 模式
pitch: 52,
rotation: 60,
center: [curr.lon, curr.lat],
features: ['bg', 'road'],
zoom: 17,
buildingAnimation:true,
mapStyle: "amap://styles/fresh"
});
_this.position.lng = curr.lon;
_this.position.lat = curr.lat;
addPlugin();
map.AmbientLight = new AMap.Lights.AmbientLight([1, 1, 1], 0.5);
map.DirectionLight = new AMap.Lights.DirectionLight([1, -1, 2], [1, 1, 1], 0.8);
object3Dlayer = new AMap.Object3DLayer();
map.add(object3Dlayer);
for (let bx = 0; bx < _this.map3d.buildings.length; bx++) {
let building = _this.map3d.buildings[bx];
let coordinates = building.coordinates;
// 没有描边 没有层高 没有楼层数 不显示立体
if (coordinates === null || coordinates === ''
|| building.firstHeight == null || building.floorCount <= 0) {
continue;
}
let cArrays = coordinates.split(';');
let pathList = [];
for (let pc = 0; pc < cArrays.length; pc++) {
let cArray = cArrays[pc].split(',');
pathList.push([cArray[0], cArray[1]]);
}
_this.drawBuilding(building.buildingId, pathList, building);
}
drawProjectLand(_this.map3d.landPoints);
// 第一次进入默认显示一个物业
if (_this.visible.firstEntry === true) {
if (emptyUtil.arrayNotEmpty( _this.sidebarInfo.buildingIds)) {
_this.globalVariable.buildingId = _this.sidebarInfo.buildingIds[0];
_this.showUnitTab(null);
}
_this.visible.firstEntry = false;
}
// prism 拾取
map.on('mousedown', function (ev) {
var pixel = ev.pixel;
var px = new AMap.Pixel(pixel.x, pixel.y);
var obj = map.getObject3DByContainerPos(px, [object3Dlayer], false) || {};
// 选中的 object3D 对象,这里为当前 Mesh
var object = obj.object;
// 被拾取到的对象和拾取射线的交叉点的3D坐标
clickMesh(object, _this);
});
$('.amap-controlbar-zoom').hide();
$('#map-3d > div.amap-controls > div.amap-maptypecontrol').hide();
}
function updateMeshColor(pool, color, action) {
if (action === 'change') {
pool.change = 1;
}
if (action === 'reset') {
pool.change = 0;
}
let mesh = pool.mesh;
let vertexColors = mesh.geometry.vertexColors;
let len = vertexColors.length;
for (let i = 0; i <= len / 4; i++) {
let r = color[0];
let g = color[1];
let b = color[2];
let a = color[3];
// 不能重新赋值,只允许修改内容
vertexColors.splice(i * 4, 4, r, g, b, a);
}
mesh.needUpdate = true;
mesh.reDraw();
}
function drawTextFlag(building, fc, secondH, _this) {
let th = (building.firstHeight + ((fc - 1) * secondH)) * sdh;
let text = new AMap.Text({
text: emptyUtil.isEmpty(building.anotherName) ? ('#' + building.buildingId) : building.anotherName,
verticalAlign: 'bottom',
position: [_this.center.lng, _this.center.lat],
height: th,
style: {
'background-color': 'transparent',
'-webkit-text-stroke': 'white',
'-webkit-text-stroke-width': '0.4px',
'text-align': 'center',
'border': 'none',
'color': 'white',
'font-size': '14px',
}
});
text.setMap(map);
texts.push(text);
}
function initMesh(paths, building, _this) {
let fc = building.floorCount;
let secondH;
if (building.secondAndMoreHeight == null) {
secondH = 3;
} else {
secondH = building.secondAndMoreHeight;
}
// 物业标记
drawTextFlag(building, fc, secondH, _this);
for (let fci = 1; fci <= fc; fci++) {
drawFloor3dModel(paths, building, fci, secondH);
}
}
function drawMiddleLayer(bounds, color, startH, endH, transparent) {
let mesh = new AMap.Object3D.Mesh();
let geometry = mesh.geometry;
let vertices = geometry.vertices;
let vertexColors = geometry.vertexColors;
let faces = geometry.faces;
let vertexLength = bounds.length * 2;
let verArr = [];
bounds.forEach(function (lngLat, index) {
let g20 = map.lngLatToGeodeticCoord(lngLat);
verArr.push([g20.x, g20.y]);
// 构建顶点-底面顶点
vertices.push(g20.x, g20.y, -startH);
// 构建顶点-顶面顶点
vertices.push(g20.x, g20.y, -endH);
vertexColors.push.apply(vertexColors, color);
vertexColors.push.apply(vertexColors, color);
let bottomIndex = index * 2;
let topIndex = bottomIndex + 1;
let nextBottomIndex = (bottomIndex + 2) % vertexLength;
let nextTopIndex = (bottomIndex + 3) % vertexLength;
//侧面三角形1
faces.push(bottomIndex, topIndex, nextTopIndex);
//侧面三角形2
faces.push(bottomIndex, nextTopIndex, nextBottomIndex);
drawVerticalBar(lngLat, endH);
});
// 物业描边 不使用黑色
if (color === landFaceC) {
drawLine(bounds, endH, landLineC, 1);
} else {
drawLine(bounds, endH, floorLineC, 1);
}
// 设置顶面,根据顶点拆分三角形
let triangles = AMap.GeometryUtil.triangulateShape(verArr);
for (let v = 0; v < triangles.length; v += 3) {
let a = triangles[v];
let b = triangles[v + 2];
let c = triangles[v + 1];
faces.push(a * 2 + 1, b * 2 + 1, c * 2 + 1);
}
mesh.backOrFront = 'both';
mesh.transparent = transparent;
object3Dlayer.add(mesh);
return mesh;
}
function drawProjectLand(landPoints) {
if (emptyUtil.arrayIsEmpty(landPoints)) {
return;
}
let landBounds = landPoints.map(function (p) {
return new AMap.LngLat(p.lng, p.lat);
});
drawMiddleLayer(landBounds, landFaceC, 0, 1, true);
}
/**
* 画具体楼层3d模型
*/
function drawFloor3dModel(path, building, fci, secondH) {
let bounds = path.map(function (p) {
return new AMap.LngLat(p[0], p[1]);
});
if (fci === 1) {
// 最底层边线
drawLine(bounds, 0, floorLineC, 1);
}
let unit = null;
if (building.unitList != null && building.unitList.length > 0) {
unit = building.unitList.find(function (unitOne) {
return unitOne.floorNumber === fci && unitOne.unoccupiedArea != null && unitOne.unoccupiedArea > 0;
});
}
let startH = 0;
let endH = 0;
let fh = building.firstHeight * sdh;
if (fci === 1) {
endH = fh;
} else {
startH = fh + ((fci - 2) * sdh * secondH);
endH = fh + ((fci - 1) * sdh * secondH);
}
let color = noUnAreaColor;
// 当前有空置面积
if (unit != null) {
color = hasUnAreaColor;
drawMiddleLayer(bounds, hasUnAreaColor, startH, startH + 5, false);
} else {
drawMiddleLayer(bounds, noUnAreaColor, startH, startH + 5, false);
}
let mesh = drawMiddleLayer(bounds, color, startH, endH, true);
let pool3d = {};
pool3d.mesh = mesh;
pool3d.change = 0; // 优化物业太多时 重置颜色很慢
pool3d.color = color;
pool3d.fc = fci;
pool3d.building = building;
pool3ds.push(pool3d);
}
/**
* 点击具体单元改变模型颜色
* @param buildingId
* @param fc
*/
function editUnitColor(buildingId, fc) {
let pool3d = pool3ds.find(function (one) {
return one.building.buildingId === buildingId && one.fc === fc;
});
pool3ds.forEach(function (pool) {
if (pool.change === 1) {
pool3ds.slice(pool, 1);
updateMeshColor(pool, pool.color, 'reset');
pool3ds.push(pool);
}
});
if (pool3d) {
pool3ds.slice(pool3d, 1);
updateMeshColor(pool3d, selectColor, 'change');
pool3ds.push(pool3d);
}