手动选择显示_QGIS 二次开发笔记(2)——显示图层
本文使用 Zhihu On VSCode 创作并发布
基于 QGIS 二次开发,最首要的功能就是显示图层。这是个看似非常简单的功能,但是在 QGIS 中写了非常复杂的代码,以支持各种数据源。
但是我们在二次开发中,一般不会支持那么多的数据源。这篇博客首先以 ESRI Shapefile 数据源为例,展示加载图层的过程。
博客以创建好的工程开始,创建工程的过程网上资料很多,这里就不再赘述了。
添加地图框
要想显示图层,首先要有一个显示图层的地方。在 QGIS SDK 中,使用类 QgsMapCanvas
显示地图。这个类需要 QT 中的 svg 组件,即
# QgsSdkApp.pro
QT += core gui xml svg
如果我们想在 QMainWindow
派生类(我的工程中创建的类名是 QgsSdkApp
,以后直接用这个名称)添加一个 QgsMapCanvas
类型的组件,
有以下三种方法:
- 添加插件的方式:在 QT Designer 中添加插件,在 QT Designer 中绘制(我没有实现成功,理论上可以)
- 提升类型的方式:在 QT Designer 中使用“提升”功能,将 QWidget 组件提升为
QgsMapCanvas
类型 - 手动创建的方式:在 QgsSdkApp.cpp 中手动创建
QgsMapCanvas
类型的对象,添加到窗口中
下面一一展示。
提升类型的方式
选择一个组件,右键单击之后,单击“提升为”按钮,可以弹出提升窗口部件的对话框。
上面这个对话框,现在 1 所示的位置输入要提升的类型的名称,然后点击 2 位置上的按钮,在上面的列表上选中刚刚添加的提升类型,
点击 3 位置上的按钮,即可将该组件提升为指定的类型(即 QgsMapCanvas
类型)。
然后,在 cpp 文件中已经可以访问到这个类型的组件了。
手动创建的方式
手动创建的方式就是在窗口类的构造函数中,构造一个 QgsMapCanvas
类型的对象,然后添加到窗口上。
这种情况下和其他在 QT 中手动创建对象没有差别,和其他一样处理即可。
添加图层
添加了地图框(在类内使用一个指针 mMapCanvas
,指向这个地图框)之后,下面就可以来添加图层了。
首先以 ESRI Shapefile 为例,介绍一下添加 QgsVectorLayer
的基本方法。
添加矢量图层的方法
在 QgsSdkApp.ui 中可以添加一个 action ,并拖到工具栏上创建工具按钮。点击这个按钮后开始添加图层。
这个过程网上有很多教程,这里就不再赘述了。
如果要添加 ESRI Shapefile 图层,我们需要先选择这个文件。我们可以直接弹出一个文件选择对话框。
我们以选择的文件路径作为数据源的路径,文件名(不含扩展名)为图层名称。
void QgsSdkApp::on_actionShp_Layer_triggered()
{QString filePath = QFileDialog::getOpenFileName(this, tr("Open ESRI Shapefile"), tr(""), tr("ESRI Shapefile (*.shp)"));QFileInfo fileInfo(filePath);if (fileInfo.exists()){QString fileName = fileInfo.baseName();addVectorLayer(filePath, fileName, "ogr");}
}
创建 addVectorLayer()
函数,用于添加图层。我们可以以一种简单的方式添加图层,即直接创建 QgsVectorLayer
对象,并保存到一个列表(mMapLayerList
)里。
然后让地图框加载这个列表,即可显示地图。
QgsVectorLayer *QgsSdkApp::addVectorLayer(const QString &vectorLayerPath, const QString &name, const QString &providerKey, bool guiWarning)
{QgsVectorLayer* vectorLayer = new QgsVectorLayer(uri, layerName, providerKey);mMapLayerList.append(vectorLayer);mMapCanvas->setLayers(mMapLayerList);if (mMapLayerList.size() == 1){QgsMapLayer* firstLayer = mMapLayerList.first();QgsRectangle extent = mMapCanvas->mapSettings().layerExtentToOutputExtent(firstLayer, firstLayer->extent());mMapCanvas->setExtent(extent);}mMapCanvas->refresh();
}
这个函数中除了添加了地图,同时也限制了地图框地显示范围,即设置为第一个图层地显示范围。最后对地图进行了刷新。但是这种方式会遇到很多问题:
- 如果将来要设计 Layout ,那么这个地图框的内容无法同步到 Layout 中的地图框中。
- 如果图层的投影到地图框的投影有多种转换方式,那么无法选择指定投影方式(尚未实现成功)
- 如果图层有子图层,无法选择子图层(ESRI Shapefile 中不会遇到)
- 如果图层需要访问验证,无法获取图层(ESRI Shapefile 中不会遇到)
在 QGIS 中,使用以下代码以较为完善地设置添加地图层,同时支持了很多其他功能。函数中创建的图层直接添加到 QgsProject
中,以支持 Layout 等功能。
QgsVectorLayer *QgsSdkApp::addVectorLayer(const QString &vectorLayerPath, const QString &name, const QString &providerKey, bool guiWarning)
{QString baseName = QgsMapLayer::formatLayerName( name );/* Eliminate the need to instantiate the layer based on provider type.The caller is responsible for cobbling together the needed information toopen the layer*/QgsDebugMsg( "Creating new vector layer using " + vectorLayerPath+ " with baseName of " + baseName+ " and providerKey of " + providerKey );// if the layer needs authentication, ensure the master password is setbool authok = true;QRegExp rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );if ( rx.indexIn( vectorLayerPath ) != -1 ){authok = false;if ( !QgsAuthGuiUtils::isDisabled( messageBar(), messageTimeout() ) ){authok = QgsApplication::authManager()->setMasterPassword( true );}}// create the layerQgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };// Default style is loaded later in this methodoptions.loadDefaultStyle = false;QgsVectorLayer *layer = new QgsVectorLayer( vectorLayerPath, baseName, providerKey, options );if ( authok && layer && layer->isValid() ){QStringList sublayers = layer->dataProvider()->subLayers();QgsDebugMsg( QStringLiteral( "got valid layer with %1 sublayers" ).arg( sublayers.count() ) );// If the newly created layer has more than 1 layer of data available, we show the// sublayers selection dialog so the user can select the sublayers to actually load.if ( sublayers.count() > 1 &&! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) &&! vectorLayerPath.contains( QStringLiteral( "layername=" ) ) ){QList< QgsMapLayer * > addedLayers = askUserForOGRSublayers( layer );// The first layer loaded is not useful in that case. The user can select it in// the list if he wants to load it.delete layer;layer = addedLayers.isEmpty() ? nullptr : qobject_cast< QgsVectorLayer * >( addedLayers.at( 0 ) );for ( QgsMapLayer *l : addedLayers )askUserForDatumTransform( l->crs(), QgsProject::instance()->crs(), l );}else{// Register this layer with the layers registryQList<QgsMapLayer *> myList;//set friendly name for datasources with only one layerQStringList sublayers = layer->dataProvider()->subLayers();if ( !sublayers.isEmpty() ){setupVectorLayer( vectorLayerPath, sublayers, layer,providerKey, options );}myList << layer;QgsProject::instance()->addMapLayers( myList );askUserForDatumTransform( layer->crs(), QgsProject::instance()->crs(), layer );bool ok;layer->loadDefaultStyle( ok );layer->loadDefaultMetadata( ok );}}else{if ( guiWarning ){QString message = layer->dataProvider() ? layer->dataProvider()->error().message( QgsErrorMessage::Text ) : tr( "Invalid provider" );QString msg = tr( "The layer %1 is not a valid layer and can not be added to the map. Reason: %2" ).arg( vectorLayerPath, message );visibleMessageBar()->pushMessage( tr( "Layer is not valid" ), msg, Qgis::Critical, messageTimeout() );}delete layer;return nullptr;}// Let render() do its own cursor management// QApplication::restoreOverrideCursor();return layer;
}
注意其中的
QgsProject::instance()->addMapLayers( myList );
一行,其实在 QGIS 中所有图层都是通过QgsProject
进行管理的,一般来说用户无需自己管理。
QGIS SDK 也提供了QgsLayerTreeView
等一套类,用于显示图层的层级结构,使用也比较方便。
但是如果有一些特别需要的功能,再进行自己管理。
这个函数中还需要其他几个函数,分别定义如下:
QList<QgsMapLayer *> QgsSdkApp::askUserForOGRSublayers(QgsVectorLayer *layer)
{QList<QgsMapLayer *> result;if ( !layer ){layer = qobject_cast<QgsVectorLayer *>( activeLayer() );if ( !layer || layer->providerType() != QLatin1String( "ogr" ) )return result;}QStringList sublayers = layer->dataProvider()->subLayers();QgsSublayersDialog::LayerDefinitionList list;QMap< QString, int > mapLayerNameToCount;bool uniqueNames = true;int lastLayerId = -1;const auto constSublayers = sublayers;for ( const QString &sublayer : constSublayers ){// OGR provider returns items in this format:// <layer_index>:<name>:<feature_count>:<geom_type>QStringList elements = splitSubLayerDef( sublayer );if ( elements.count() >= 4 ){QgsSublayersDialog::LayerDefinition def;def.layerId = elements[0].toInt();def.layerName = elements[1];def.count = elements[2].toInt();def.type = elements[3];// ignore geometry column name at elements[4]if ( elements.count() >= 6 )def.description = elements[5];if ( lastLayerId != def.layerId ){int count = ++mapLayerNameToCount[def.layerName];if ( count > 1 || def.layerName.isEmpty() )uniqueNames = false;lastLayerId = def.layerId;}list << def;}else{QgsDebugMsg( "Unexpected output from OGR provider's subLayers()! " + sublayer );}}// Check if the current layer uri contains the// We initialize a selection dialog and display it.QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Ogr, QStringLiteral( "ogr" ), this );chooseSublayersDialog.setShowAddToGroupCheckbox( true );chooseSublayersDialog.populateLayerTable( list );if ( !chooseSublayersDialog.exec() )return result;QString name = layer->name();auto uriParts = QgsProviderRegistry::instance()->decodeUri(layer->providerType(), layer->dataProvider()->dataSourceUri() );QString uri( uriParts.value( QStringLiteral( "path" ) ).toString() );// The uri must contain the actual uri of the vectorLayer from which we are// going to load the sublayers.QString fileName = QFileInfo( uri ).baseName();const auto constSelection = chooseSublayersDialog.selection();for ( const QgsSublayersDialog::LayerDefinition &def : constSelection ){QString layerGeometryType = def.type;QString composedURI = uri;if ( uniqueNames ){composedURI += "|layername=" + def.layerName;}else{// Only use layerId if there are ambiguities with namescomposedURI += "|layerid=" + QString::number( def.layerId );}if ( !layerGeometryType.isEmpty() ){composedURI += "|geometrytype=" + layerGeometryType;}QgsDebugMsg( "Creating new vector layer using " + composedURI );QString name = fileName + " " + def.layerName;QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };options.loadDefaultStyle = false;QgsVectorLayer *layer = new QgsVectorLayer( composedURI, name, QStringLiteral( "ogr" ), options );if ( layer && layer->isValid() ){result << layer;}else{QString msg = tr( "%1 is not a valid or recognized data source" ).arg( composedURI );visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::Critical, messageTimeout() );delete layer;}}if ( !result.isEmpty() ){QgsSettings settings;bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), true ).toBool();bool newLayersVisible = settings.value( QStringLiteral( "/qgis/new_layers_visible" ), true ).toBool();QgsLayerTreeGroup *group = nullptr;if ( addToGroup )group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, name );QgsProject::instance()->addMapLayers( result, ! addToGroup );for ( QgsMapLayer *l : qgis::as_const( result ) ){bool ok;l->loadDefaultStyle( ok );l->loadDefaultMetadata( ok );if ( addToGroup )group->addLayer( l );}// Respect if user don't want the new group of layers visible.if ( addToGroup && ! newLayersVisible )group->setItemVisibilityCheckedRecursive( newLayersVisible );}return result;
}bool QgsSdkApp::askUserForDatumTransform(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsMapLayer *layer)
{Q_ASSERT( qApp->thread() == QThread::currentThread() );QString title;if ( layer ){// try to make a user-friendly (short!) identifier for the layerQString layerIdentifier;if ( !layer->name().isEmpty() ){layerIdentifier = layer->name();}else{const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );if ( parts.contains( QStringLiteral( "path" ) ) ){const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );layerIdentifier = fi.fileName();}else if ( layer->dataProvider() ){const QgsDataSourceUri uri( layer->source() );layerIdentifier = uri.table();}}if ( !layerIdentifier.isEmpty() )title = tr( "Select Transformation for %1" ).arg( layerIdentifier );}return QgsDatumTransformDialog::run( sourceCrs, destinationCrs, this, mMapCanvas, title );
}static QStringList splitSubLayerDef( const QString &subLayerDef )
{return subLayerDef.split( QgsDataProvider::sublayerSeparator() );
}static void setupVectorLayer( const QString &vectorLayerPath,const QStringList &sublayers,QgsVectorLayer *&layer,const QString &providerKey,QgsVectorLayer::LayerOptions options )
{//set friendly name for datasources with only one layerQgsSettings settings;QStringList elements = splitSubLayerDef( sublayers.at( 0 ) );QString rawLayerName = elements.size() >= 2 ? elements.at( 1 ) : QString();QString subLayerNameFormatted = rawLayerName;if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() ){subLayerNameFormatted = QgsMapLayer::formatLayerName( subLayerNameFormatted );}if ( elements.size() >= 4 && layer->name().compare( rawLayerName, Qt::CaseInsensitive ) != 0&& layer->name().compare( subLayerNameFormatted, Qt::CaseInsensitive ) != 0 ){layer->setName( QStringLiteral( "%1 %2" ).arg( layer->name(), rawLayerName ) );}// Systematically add a layername= option to OGR datasets in case// the current single layer dataset becomes layer a multi-layer one.// Except for a few select extensions, known to be always single layer dataset.QFileInfo fi( vectorLayerPath );QString ext = fi.suffix().toLower();if ( providerKey == QLatin1String( "ogr" ) &&ext != QLatin1String( "shp" ) &&ext != QLatin1String( "mif" ) &&ext != QLatin1String( "tab" ) &&ext != QLatin1String( "csv" ) &&ext != QLatin1String( "geojson" ) &&! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) &&! vectorLayerPath.contains( QStringLiteral( "layername=" ) ) ){auto uriParts = QgsProviderRegistry::instance()->decodeUri(layer->providerType(), layer->dataProvider()->dataSourceUri() );QString composedURI( uriParts.value( QStringLiteral( "path" ) ).toString() );composedURI += "|layername=" + rawLayerName;auto newLayer = qgis::make_unique<QgsVectorLayer>( composedURI, layer->name(), QStringLiteral( "ogr" ), options );if ( newLayer && newLayer->isValid() ){delete layer;layer = newLayer.release();}}
}
这个函数中还需要以下几个 Getter 函数,我们在窗口中提供对应的组件即可:
QgsMessageBar* messageBar()
: 可以在状态栏上创建一个QgsMessageBar
的对象,用这个函数获取int messageTimeout()
: 可以直接返回 500 ,或根据配置文件、设置等返回QgsMessageBar* visibleMessageBar()
: 可直接返回状态栏上的QgsMessageBar
对象QgsMapLayer* activeLayer()
: 需要使用QgsLayerTreeView
类获取,下面详述
但是现在,还不能在地图上显示,需要将 QgsMapCanvas
和 QgsProject
建立关联,才能将 QgsProject
中的图层同步到 QgsMapCanvas
中。
使用的方法是在构造函数中添加如下代码:
QgsSdkApp::QgsSdkApp(QWidget *parent): QMainWindow(parent), ui(new Ui::QgsSdkApp)
{ui->setupUi(this);mMapCanvas = ui->centralwidget;mMapCanvas->setObjectName(QStringLiteral("theMapCanvas"));/** [BEGIN] 添加的用于将 `QgsMapCanvas` 和 `QgsProject` 建立关联的代码 */mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, this);/** [END] */connect(ui->actionAdd_Shp_Layer, &QAction::triggered, this, &QgsSdkApp::on_actionShp_Layer_triggered);
}
这样添加了图层之后,就可以在地图上显示了。
添加其他图层
其他图层的添加方法,都可以从 QGIS 的代码中进行参考。在 qgisapp.cpp 文件中,有这个函数
void QgisApp::dataSourceManager( const QString &pageName )
{if ( ! mDataSourceManagerDialog ){mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mBrowserModel, this, mapCanvas() );connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged );connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) );connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString const &, QString const &, QString const & ) ),this, SLOT( addVectorLayer( QString const &, QString const &, QString const & ) ) );connect( mDataSourceManagerDialog, SIGNAL( addVectorLayers( QStringList const &, QString const &, QString const & ) ),this, SLOT( addVectorLayers( QStringList const &, QString const &, QString const & ) ) );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addMeshLayer, this, &QgisApp::addMeshLayer );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showStatusMessage, this, &QgisApp::showStatusMessage );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addDatabaseLayers, this, &QgisApp::addDatabaseLayers );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::replaceSelectedVectorLayer, this, &QgisApp::replaceSelectedVectorLayer );connect( mDataSourceManagerDialog, static_cast<void ( QgsDataSourceManagerDialog::* )()>( &QgsDataSourceManagerDialog::addRasterLayer ), this, static_cast<void ( QgisApp::* )()>( &QgisApp::addRasterLayer ) );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::handleDropUriList, this, &QgisApp::handleDropUriList );connect( this, &QgisApp::newProject, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::updateProjectHome );connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::openFile, this, &QgisApp::openFile );}else{mDataSourceManagerDialog->reset();}// Try to open the dialog on a particular pageif ( ! pageName.isEmpty() ){mDataSourceManagerDialog->openPage( pageName );}if ( QgsSettings().value( QStringLiteral( "/qgis/dataSourceManagerNonModal" ), true ).toBool() ){mDataSourceManagerDialog->show();}else{mDataSourceManagerDialog->exec();}
}
这个函数中有 addRasterLayer
addVectorLayer
addMeshLayer
等函数,是添加不同类型的图层的方法。可以直接查看这些方法,学习其中的方法,放到工程中来。
在下篇博客中,我计划介绍添加 CSV 类型数据的方法。
显示图层树
一般情况下我们都需要使用图层树来对图层进行管理。下面我们就在界面上添加 QgsLayerTreeView
对象。
我们首先在界面上创建一个 QWidget 组件,提升为 QgsLayerTreeView
类型。然后再构造函数中给其设置 Model 等。
QgsSdkApp::QgsSdkApp(QWidget *parent) : QMainWindow(parent), ui(new Ui::QgsSdkApp)
{ui->setupUi(this);mMapCanvas = ui->centralwidget;mMapCanvas->setObjectName(QStringLiteral("theMapCanvas"));mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, this);/** [BEGIN] 设置 QgsLayerTreeView 的 Model */QgsLayerTreeModel* model = new QgsLayerTreeModel(QgsProject::instance()->layerTreeRoot(), this);ui->layerTreeView->setModel(model);ui->layerTreeView->setObjectName(QStringLiteral( "theLayerTreeView" ));/** [END] */mInfoBar = new QgsMessageBar(this);ui->statusbar->addWidget(mInfoBar);connect(ui->actionAdd_Shp_Layer, &QAction::triggered, this, &QgsSdkApp::on_actionShp_Layer_triggered);
}
QT 中采用的 MVC 模型。 QT 中提供了 QTreeView 、 QListView 、 QTableView 等视图(View),
也提供了 QAbstractItemModel 、 QAbstractListModel 、 QAbstractTableModel 三种模型(Model),
也会提供了一些 Delegate 委托(相当于控制器 Controller)。
但是我们这个图层树仅仅有一个最基本的功能,而在 QGIS 中排序、右键菜单丰富的功能。
对于右键菜单,QGIS 中使用了 Provider 的方式提供右键菜单的菜单项,我们需要将这些 Provider 的代码复制过来,添加到工程中。
// qgisapp.cpp [4493行]
mLayerTreeView->setMenuProvider( new QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );
对于排序等其他功能,我们可以按需添加。
手动选择显示_QGIS 二次开发笔记(2)——显示图层相关推荐
- revit二次开发 点投影到面_revit二次开发笔记
Revit2016 二次开发笔记 1. 选中图元 1.1 事先选择某类图元 ( Wall ) Autodesk.Revit.UI.UIApplication revit = commandData.A ...
- ArcGIS二次开发——地图居中显示
ArcGIS二次开发--地图居中显示 一.创建 Engine 应用程序 1.启动 Visual Studio 2012,从"文件"->"新建"选中&quo ...
- Revit二次开发笔记
Revit二次开发笔记: 配置:以revit 2021为例 Visual studio中的配置 以下是如果要翻成EnergyPlus模型需要的配置 如何安装Addin Manger和lookup插件 ...
- php二次跳转,discuz二次开发笔记(二)------跳转函数运用,discuz二次开发_PHP教程...
discuz二次开发笔记(二)------跳转函数运用,discuz二次开发 前几天在增加修改功能时,突然用到一个提示函数,有点不理解,看了他的由来后果断做下笔记,感觉这在以后的开发中肯定还是要用的上 ...
- NX二次开发-设置WCS显示UF_CSYS_set_wcs_display
NX二次开发-设置WCS显示UF_CSYS_set_wcs_display NX9+VS2012#include <uf.h> #include <uf_csys.h>UF_i ...
- vs如何显示arcgis 二次开发工具控件
在开发项目时,想运用wpf进行arcgis窗体二次开发的,但是创建了wpf项目后发现工具控件没有显示,这是怎么回事呢? 其实这是因为工具箱中并没有加载控件,那如何添加进去呢? 其实我们可以右击&quo ...
- (dede)织梦系统二次开发笔记
(dede)织梦系统二次开发记录 --soulsjie 一.模板常用文件说明 模板文件都在文件夹templets下,我们以默认模板(default)为例,对模板文件结构进行分析: 首页模板文件目录 \ ...
- “单点登录CAS集成应用系统”二次开发笔记
CAS 二次开发踩过的坑 cas登出问题: 如果直接CAS的logout话,会出现注销成功页面,其实大部分情况下这个页面是没有必要的,更多的需要可能是退出后显示登录页面,并且登录成功后还是会进入到之前 ...
- Vissim-Python二次开发笔记
Vissim简述 Visism是德国PTV公司开发的一款微观交通仿真软件 ,功能强大,支持可视化界面操作,常用于城市交通仿真,在交通规划设计.学术研究中使用较多.Vissim提供了COM组件对象接口, ...
最新文章
- ORACLE中对LONG类型进行处理的方法
- java性能优化读书笔记(1)
- MYSQL: MERGE引擎实现多分表的联合
- Optical_Flow(1)
- 地图投影系列介绍(一)----地球空间模型
- C++学习之路 | PTA乙级—— 1003 我要通过! (20分)(精简)
- html字符串变量,字符串变量中的Python HTML
- nginx 在ubuntu 上的启动,停止,重启
- python map filter reduce
- Ubuntu修改su和sudo密码
- 寻求生态保护与矿产开发平衡点 青海给出“绿色方案”
- 微信微调助手WeChatTweak for mac(微信多开和防撤回工具)最新版
- 用C写邮箱密码暴力破解器
- 三门问题与神奇的贝叶斯大脑
- 【TcaplusDB知识库】表操作—如何克隆表结构
- C语言基础知识复习(1)
- C语言 正序输出数字
- SecureCRT工具介绍
- 2010年搜索引擎的发展状况
- Kotlin 基础语法(《第一行代码(第三版)》第二章读书笔记)
热门文章
- Several frontend roundtrip diagram - synchronous and Asynchronous
- Cloud for Customer的主页加载逻辑
- Cloud for Customer的设置加载机制
- SAP S/4HANA的扩展字段的渲染逻辑
- Launch debug in SWI1 workflow
- Why I could not put extension fields done on CUSTOMER_H to WebUI
- SAP CRM呼叫中心里confirm按钮的实现逻辑
- ABAP, Java和JavaScript三种语言的比较
- OpenFOAM边界类型(终极详细介绍)
- 安装了vmware tools后,自动调整大小-自动适应客户机仍为灰色(终极解决方案)