自从上次批量导出gltf模型后,需求便随之而来,就是在线添加模型,并且能够更改模型的参数,比如大小,位置等等,效仿的是某图的客户端添加二维模型.前端使用的是Extjs4.2,jQuery和三维引擎cesium,后端asp.net mvc.
前端
window显示框
function addModel() {
if (Ext.getCmp('modelPanel') !== null) {
Ext.getCmp('modelPanel').destroy();
}
var modelWindow = Ext.create('Ext.window.Window', {
title: '添加模型',
width: 820,
height: 520,
renderTo: Ext.getBody(),
plain: true,
collapsible: true, //是否可以折叠
closable: true,
layout: 'border',
items: [{
region: 'west',
xtype: 'treepanel',
title: '模型分类',
width: '15%',
store: treestore,
rootVisible: false,
listeners: {
itemclick: function(view, record, item, index, e) {
var code = record.raw.code;
var store = loadCode(code);
var gridPanles = editView(store);
var FartherGrid = modelWindow.getComponent('gridPanel');
FartherGrid.removeAll();
//添加面板
if (gridPanles != null) {
gridPanles = FartherGrid.add({
xtype: gridPanles,
closable: true
});
gridPanles.getStore().load();
}
}
}
}, {
margin: '1 1 1 1',
region: 'center',
width: '45%',
itemId: 'gridPanel',
autoScroll: true,
items: [{
xtype: 'dataview',
width: '40%',
autoScroll: true,
itemSelector: 'div.thumb-wrap',
tpl: imageTpl,
}]
}, {
xtype: 'panel',
region: 'east',
itemId: 'box1',
width: '40%',
border: false,
layout: 'fit',
listeners: {
show: function(c) {
pageInit3d();
}
},
bodyStyle: 'background-color:#E5E3DF;',
frame: false,
html: '<div id="mapPic"><div class="nav">' + '<div class="up" id="up"></div><div class="right" id="right"></div>' + '<div class="down" id="down"></div><div class="left" id="left"></div>' + '<div class="zoom" id="zoom"></div><div class="in" id="in"></div>' + '<div class="out" id="out"></div></div>' + '<img id="view-image3d" src="" border="0" style="cursor: url(/Images/openhand_8_8.cur), default;" > </div>'
}],
listeners: {
show: function(c) {
pageInit3d();
}
},
buttons: [{
text: '确认',
handler: function() {
modelWindow.destroy();
showmodulemessagefn("双击点选位置添加模型");
$(document).bind("dblclick",
function(e) {
var NW = new Cesium.Cartesian2(e.pageX, e.pageY);
var pick1 = viewer.scene.globe.pick(viewer.camera.getPickRay(NW), viewer.scene);
//将三维坐标转成地理坐标
var geoPt1 = viewer.scene.globe.ellipsoid.cartesianToCartographic(pick1);
var gaodu = viewer.scene.globe.getHeight(geoPt1).toFixed(5);
//地理坐标转换为经纬度坐标
var point1 = [geoPt1.longitude / Math.PI * 180, geoPt1.latitude / Math.PI * 180];
// console.log(point1+","+gaodu);
var viewModel = {
jingdu: point1[0].toFixed(6),
weidu: point1[1].toFixed(6),
gaodu: gaodu,
heng: 0,
zong: 0,
zzz: 0,
size: 1
};
// Convert the viewModel members into knockout observables.
Cesium.knockout.track(viewModel);
createDiv = document.createElement("div");
createDiv.innerHTML = htmlStr;
createDiv.id = "toolbar";
createDiv.style.position = "absolute";
createDiv.style.right = "5px";
createDiv.style.top = "38px";
document.body.appendChild(createDiv);
var toolbar = document.getElementById('toolbar');
Cesium.knockout.applyBindings(viewModel, toolbar);
var jingdu = $("#jingdu")[0].value;
var weidu = $("#weidu")[0].value;
var gaodu = $("#height")[0].value;
var heng = $("#heng")[0].value;
var zong = $("#zong")[0].value;
var zzz = $("#xie")[0].value;
var scale = $("#size")[0].value;
var idid = uuid();
var modelMatrix = Cesium.Cartesian3.fromDegrees(point1[0], point1[1], gaodu);
if (typeof(entity) != 'undefined') { viewer.entities.remove(entity); }
var entity = AddModelss(idid, modelpath, modelMatrix, heng, zong, zzz, scale);
$("input").change(function() {
scale = $("#size")[0].value;
jingdu = $("#jingdu")[0].value;
weidu = $("#weidu")[0].value;
gaodu = $("#height")[0].value;
heng = $("#heng")[0].value;
zong = $("#zong")[0].value;
zzz = $("#xie")[0].value;
modelMatrix = Cesium.Cartesian3.fromDegrees(jingdu, weidu, gaodu);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(modelMatrix, heng, zong, zzz);
entity.model.scale = scale;
entity.position = modelMatrix;
entity.orientation = orientation;
});
$("#no").click(function() {
viewer.entities.remove(entity);
var toolbar = document.getElementById('toolbar');
toolbar.parentNode.removeChild(toolbar);
});
$("#yes").click(function() {
Ext.Ajax.request({
url: '/TDGis/uploadModel',
params: {
x: jingdu,
y: weidu,
z: gaodu,
heng: heng,
zong: zong,
xie: zzz,
scale: scale,
id: id,
idid: idid
},
method: 'POST',
timeout: 3000,
success: function(response, opts) {
// Ext.Msg.alert('提示', '添加模型成功。');
var toolbar = document.getElementById('toolbar');
toolbar.parentNode.removeChild(toolbar);
}
});
});
$(this).unbind(e);
});
}
}, {
text: '取消',
handler: function() {
modelWindow.destroy();
}
}]
}).show();
}
window中的方法
1.最左侧的tree
var treestore = new Ext.data.TreeStore({
proxy: {
type: 'ajax',
url: '/TDGis/modelType',
reader: "json"
},
autoLoad: true
});
2.单击模型树生成模型列表
//store
function loadCode(code) {
gridstore = Ext.create('Ext.data.Store', {
fields: ['name1', 'thumbnailpath', 'gltfsize', 'modelpath', 'id'],
proxy: {
type: 'ajax',
reader: 'json',
url: "/TDGis/modelGrid?code=" + code
},
//extraParams:{"code":"01"},
autoLoad: true
});
return gridstore;
}
//dataview
function editView(store) {
var selectLineGridPanel = Ext.create('Ext.view.View', {
store: store,
tpl: imageTpl,
itemSelector: 'div.thumb-wrap', //store tpl itemSelector必选项
emptyText: '暂无图片',
margin: '1 1 1 1',
multiSelect: true,
height: 310,
autoScroll: false,
overItemCls: 'x-item-over',
trackOver: true,
listeners: {
itemclick: function(view, record, item, index, e) {
thumbnailpath = record.raw.thumbnailpath;
modelpath = record.raw.modelpath;
id = record.raw.id;
gltfsize = record.raw.gltfsize;
//动态更新只需要获取到刚才建立的box的dom的src
var image = Ext.get('view-image3d');
image.dom.src = thumbnailpath;
}
}
});
return selectLineGridPanel;
}
);
dataview 的tpl模板,es6语法 ``
var imageTpl = new Ext.XTemplate(
'<tpl for=".">',
'<div style="float: left;margin: 4px; margin-right: 0;padding: 5px" class="thumb-wrap">',
'<img src="{thumbnailpath}" width="70" height="70">',
'<span style="text-align: center;display: block;overflow: hidden;" class="x-editable">大小:{gltfsize}</span>',
'</div>',
'</tpl>'
3.添加模型的提示框(出现三秒后消失)
function showmodulemessagefn(message) {
//获取显示区域的大小
var width = Ext.getBody().getViewSize().width;
var trim = ' ';
//设置div中显示的文字,并渐变显示,然后三秒后渐变隐藏
$("#showmodulemessage").html(trim + message + trim).show(300).delay(3000).hide(300);
//获取有多少个文字,然后根据文字多少算出中间宽度,加40上因为左右各两个空格
var messagewidth = message.length * 10 + 40;
//设置提示框的显示位置,不平在屏幕中间,垂直在工具条下面。
$("#showmodulemessage").css({
left: width / 2 - messagewidth,
top: 110
});
}
4.动态添加div节点,用于显示调整模型信息
//多行字符串
function hereDoc(f) {
return f.toString().replace(/^[^\/]+\/\*!?\s?/, '').replace(/\*\/[^\/]+$/, '');
}
var htmlStr = hereDoc(function() {
/*
<table>
<tbody><tr>
<td width="65px"><font color="white">经度</font></td>
<td>
<input id="jingdu" type="range" min="-180" max="180" step="0.000001" data-bind="value: jingdu, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: jingdu">
</td>
</tr>
<tr>
<td><font color="white">纬度</font></td>
<td>
<input id="weidu" type="range" min="-90" max="90" step="0.000001" data-bind="value: weidu, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: weidu">
</td>
</tr>
<tr>
<td><font color="white">高度</font></td>
<td>
<input id="height" type="range" min="-50" max="1000" step="0.01" data-bind="value: gaodu, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: gaodu">
</td>
</tr>
<tr>
<td><font color="white">横向旋转<font></td>
<td>
<input id="heng" type="range" min="0" max="7" step="0.0001" data-bind="value: heng, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: heng">
</td>
</tr>
<tr>
<td><font color="white">纵向旋转</font></td>
<td>
<input id="zong" type="range" min="0" max="7" step="0.0001" data-bind="value: zong, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: zong">
</td>
</tr>
<tr>
<td><font color="white">斜旋转<font></td>
<td>
<input id="xie" type="range" min="0" max="7" step="0.0001" data-bind="value: zzz, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: zzz">
</td>
</tr>
<tr>
<td><font color="white">大小<font></td>
<td>
<input id="size" type="range" min="0" max="50" step="0.0001" value = "5" data-bind="value: size, valueUpdate: 'input'">
<input type="text" size="5" value = "5" data-bind="value: size">
</td>
</tr>
<tr>
<td>
<button id="yes" class="cesium-button1">添加</button>
</td>
<td>
<button id="no" class="cesium-button1">取消</button>
</td>
</tr>
</tbody></table>
*/
});
5.生成唯一值
//生成UUID
function uuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
uuid1 = s.join("");
return uuid1;
}
6.添加模型的方法
function AddModelss(id, modelUrl, center, zzz, yyy, xxx, scale) {
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
// var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706); //位置
var heading = Cesium.Math.toRadians(zzz); //绕垂直于地心的轴旋转 摇头
var pitch = Cesium.Math.toRadians(yyy); //绕纬度线旋转 点头
var roll = Cesium.Math.toRadians(xxx); //绕经度线旋转 歪头
var orientation = Cesium.Transforms.headingPitchRollQuaternion(center, heading, pitch, roll);
var entity = viewer.entities.add({
id: id,
position: center,
orientation: orientation,
model: {
uri: modelUrl,
scale: scale
}
});
return entity;
}
7.图片操作方法
/**
* 初始化
*/
function pageInit3d() {
var image = Ext.get('view-image3d');
if (image !== null) {
Ext.get('view-image3d').on({
'mousedown': { fn: function() { this.setStyle('cursor', 'url(/Images/openhand_8_8.cur),default;'); }, scope: image },
'mouseup': { fn: function() { this.setStyle('cursor', 'url(/Images/openhand_8_8.cur),move;'); }, scope: image },
'dblclick': {
fn: function() {
zoom3d(image, true, 1.2);
}
}
});
new Ext.dd.DD(image, 'pic');
//image.center();//图片居中
//获得原始尺寸
image.osize = {
width: image.getWidth(),
height: image.getHeight()
};
Ext.get('up').on('click', function() { imageMove3d('up', image); }); //向上移动
Ext.get('down').on('click', function() { imageMove3d('down', image); }); //向下移动
Ext.get('left').on('click', function() { imageMove3d('left', image); }); //左移
Ext.get('right').on('click', function() { imageMove3d('right', image); }); //右移动
Ext.get('in').on('click', function() { zoom3d(image, true, 1.5); }); //放大
Ext.get('out').on('click', function() { zoom3d(image, false, 1.5); }); //缩小
Ext.get('zoom').on('click', function() { image.center(); }); //还原
}
}
/**
* 图片移动
*/
function imageMove3d(direction, el) {
el.move(direction, 50, true);
}
/**
*
* @param el 图片对象
* @param type true放大,false缩小
* @param offset 量
*/
function zoom3d(el, type, offset) {
var width = el.getWidth();
var height = el.getHeight();
var xy = el.getXY();
var nwidth = type ? (width * offset) : (width / offset);
var nheight = type ? (height * offset) : (height / offset);
var left = type ? -((nwidth - width) / 2) : ((width - nwidth) / 2);
var top = type ? -((nheight - height) / 2) : ((height - nheight) / 2);
el.animate({
from: {
x: xy[0],
y: xy[1],
height: height,
width: width,
},
to: {
//left: left,
//top: top,
x: xy[0] + left,
y: xy[1] + top,
height: nheight,
width: nwidth
}
});
}
/**
* 图片还原
*/
function restore3d(el) {
var size = el.osize;
//自定义回调函数
function center(el, callback) {
el.center();
callback(el);
}
el.fadeOut({
callback: function() {
el.setSize(size.width, size.height, {
callback: function() {
center(el, function(ee) { //调用回调
ee.fadeIn();
});
}
});
}
});
}
后端(asp.net mvc)
1.模型树
public ActionResult modelType()
{
List<modelType> dataList = new List<modelType>();
gasgisEntities db = new gasgisEntities();
string selectsql = "SELECT distinct code FROM public.t_3dmodels order by code";
var ssqueryss = db.Database.SqlQuery<modelType>(selectsql);
List<modelType> kkk = ssqueryss.ToList();
for (var i = 0; i < kkk.Count; i++)
{
switch (kkk[i].code)
{
case "01": modelType daolu = new modelType
{
code = kkk[i].code,
text = "道路",
leaf = true
}; dataList.Add(daolu); break;
case "03": modelType gongchang = new modelType
{
code = kkk[i].code,
text = "工厂",
leaf = true
}; dataList.Add(gongchang); break;
case "02": modelType jumin = new modelType
{
code = kkk[i].code,
text = "居民楼",
leaf = true
}; dataList.Add(jumin); break;
case "04": modelType shangye = new modelType
{
code = kkk[i].code,
text = "商业楼",
leaf = true
}; dataList.Add(shangye); break;
case "05": modelType weiqiang = new modelType
{
code = kkk[i].code,
text = "围墙",
leaf = true
}; dataList.Add(weiqiang); break;
case "06": modelType zhengfu = new modelType
{
code = kkk[i].code,
text = "政府",
leaf = true
}; dataList.Add(zhengfu); break;
case "07": modelType others = new modelType
{
code = kkk[i].code,
text = "其他建筑",
leaf = true
}; dataList.Add(others); break;
case "08": modelType caoping = new modelType
{
code = kkk[i].code,
text = "草坪",
leaf = true
}; dataList.Add(caoping); break;
case "09": modelType huachi = new modelType
{
code = kkk[i].code,
text = "花池",
leaf = true
}; dataList.Add(huachi); break;
case "10": modelType lvhuadai = new modelType
{
code = kkk[i].code,
text = "绿化带",
leaf = true
}; dataList.Add(lvhuadai); break;
case "11": modelType shu = new modelType
{
code = kkk[i].code,
text = "树",
leaf = true
}; dataList.Add(shu); break;
case "12": modelType qita = new modelType
{
code = kkk[i].code,
text = "其他设施",
leaf = true
}; dataList.Add(qita); break;
}
}
JsonResult result = new JsonResult();
result.Data = dataList;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
2.模型列表
public ActionResult modelGrid(string code )
{
gasgisEntities db = new gasgisEntities();
string selectsql = "";
if (code == null || code == "")
{
selectsql = "SELECT id,name1,modelpath,thumbnailpath,gltfsize FROM public.t_3dmodels order by order1";
}
else
{
selectsql = "SELECT id,name1,modelpath,thumbnailpath,gltfsize FROM public.t_3dmodels where code ='" + code + "' order by order1";
}
var ssqueryss = db.Database.SqlQuery<modelGrid>(selectsql);
List<modelGrid> kkk = ssqueryss.ToList();
JsonResult result = new JsonResult();
result.Data = kkk;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;//必选允许get请求
return result;
}
3.上传模型
public string uploadModel(string idid, string id, decimal x, decimal y, decimal z, string heng, string zong, string xie, string scale)
{
gasgisEntities db = new gasgisEntities();
string ins = "insert into public.t_3dgismodels (idid,id,x,y,z,heng,zong,xie,scale) values ('" + idid + "','" + id + "'," + x + "," + y + "," + z + ",'" + heng + "','" + zong + "','" + xie + "','" + scale + "')"; ;
db.Database.ExecuteSqlCommand(ins);
modelInd();
return "ok";
}
生成索引,用于动态加载的剔除模型,加快速度
public void modelInd()
{
gasgisEntities saveSystemCodeDB = new gasgisEntities();
string insql = "UPDATE public.t_3dgismodels SET ind='x:'|| CAST(TRUNC(x,4) as varchar) || ',' || 'y:' || CAST(TRUNC(y,4) as varchar) ;";
saveSystemCodeDB.Database.ExecuteSqlCommand(insql);
//return "ok";
}
数据库使用的是postgisgreSQL,后台使用的asp.net提供数据,前端使用了jQuery,Extjs的各种信息展示,三维cesium引擎,一切都是开源免费的产品,这样一个功能,很好的展示了从后台到前端是如何工作的,也是入职以来的第一个实现的功能,虽然小,但是很有意义.