本文使用 Zhihu On VSCode 创作并发布

基于 QGIS 二次开发,最首要的功能就是显示图层。这是个看似非常简单的功能,但是在 QGIS 中写了非常复杂的代码,以支持各种数据源。
但是我们在二次开发中,一般不会支持那么多的数据源。这篇博客首先以 ESRI Shapefile 数据源为例,展示加载图层的过程。

博客以创建好的工程开始,创建工程的过程网上资料很多,这里就不再赘述了。

添加地图框

要想显示图层,首先要有一个显示图层的地方。在 QGIS SDK 中,使用类 QgsMapCanvas 显示地图。这个类需要 QT 中的 svg 组件,即

# QgsSdkApp.pro
QT += core gui xml svg

如果我们想在 QMainWindow 派生类(我的工程中创建的类名是 QgsSdkApp ,以后直接用这个名称)添加一个 QgsMapCanvas 类型的组件,
有以下三种方法:

  1. 添加插件的方式:在 QT Designer 中添加插件,在 QT Designer 中绘制(我没有实现成功,理论上可以)
  2. 提升类型的方式:在 QT Designer 中使用“提升”功能,将 QWidget 组件提升为 QgsMapCanvas 类型
  3. 手动创建的方式:在 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();
}

这个函数中除了添加了地图,同时也限制了地图框地显示范围,即设置为第一个图层地显示范围。最后对地图进行了刷新。但是这种方式会遇到很多问题:

  1. 如果将来要设计 Layout ,那么这个地图框的内容无法同步到 Layout 中的地图框中。
  2. 如果图层的投影到地图框的投影有多种转换方式,那么无法选择指定投影方式(尚未实现成功)
  3. 如果图层有子图层,无法选择子图层(ESRI Shapefile 中不会遇到)
  4. 如果图层需要访问验证,无法获取图层(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 函数,我们在窗口中提供对应的组件即可:

  1. QgsMessageBar* messageBar() : 可以在状态栏上创建一个 QgsMessageBar 的对象,用这个函数获取
  2. int messageTimeout() : 可以直接返回 500 ,或根据配置文件、设置等返回
  3. QgsMessageBar* visibleMessageBar() : 可直接返回状态栏上的 QgsMessageBar 对象
  4. QgsMapLayer* activeLayer() : 需要使用 QgsLayerTreeView 类获取,下面详述

但是现在,还不能在地图上显示,需要将 QgsMapCanvasQgsProject 建立关联,才能将 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)——显示图层相关推荐

  1. revit二次开发 点投影到面_revit二次开发笔记

    Revit2016 二次开发笔记 1. 选中图元 1.1 事先选择某类图元 ( Wall ) Autodesk.Revit.UI.UIApplication revit = commandData.A ...

  2. ArcGIS二次开发——地图居中显示

    ArcGIS二次开发--地图居中显示 一.创建 Engine 应用程序 1.启动 Visual Studio 2012,从"文件"->"新建"选中&quo ...

  3. Revit二次开发笔记

    Revit二次开发笔记: 配置:以revit 2021为例 Visual studio中的配置 以下是如果要翻成EnergyPlus模型需要的配置 如何安装Addin Manger和lookup插件 ...

  4. php二次跳转,discuz二次开发笔记(二)------跳转函数运用,discuz二次开发_PHP教程...

    discuz二次开发笔记(二)------跳转函数运用,discuz二次开发 前几天在增加修改功能时,突然用到一个提示函数,有点不理解,看了他的由来后果断做下笔记,感觉这在以后的开发中肯定还是要用的上 ...

  5. 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 ...

  6. vs如何显示arcgis 二次开发工具控件

    在开发项目时,想运用wpf进行arcgis窗体二次开发的,但是创建了wpf项目后发现工具控件没有显示,这是怎么回事呢? 其实这是因为工具箱中并没有加载控件,那如何添加进去呢? 其实我们可以右击&quo ...

  7. (dede)织梦系统二次开发笔记

    (dede)织梦系统二次开发记录 --soulsjie 一.模板常用文件说明 模板文件都在文件夹templets下,我们以默认模板(default)为例,对模板文件结构进行分析: 首页模板文件目录 \ ...

  8. “单点登录CAS集成应用系统”二次开发笔记

    CAS 二次开发踩过的坑 cas登出问题: 如果直接CAS的logout话,会出现注销成功页面,其实大部分情况下这个页面是没有必要的,更多的需要可能是退出后显示登录页面,并且登录成功后还是会进入到之前 ...

  9. Vissim-Python二次开发笔记

    Vissim简述 Visism是德国PTV公司开发的一款微观交通仿真软件 ,功能强大,支持可视化界面操作,常用于城市交通仿真,在交通规划设计.学术研究中使用较多.Vissim提供了COM组件对象接口, ...

最新文章

  1. ORACLE中对LONG类型进行处理的方法
  2. java性能优化读书笔记(1)
  3. MYSQL: MERGE引擎实现多分表的联合
  4. Optical_Flow(1)
  5. 地图投影系列介绍(一)----地球空间模型
  6. C++学习之路 | PTA乙级—— 1003 我要通过! (20分)(精简)
  7. html字符串变量,字符串变量中的Python HTML
  8. nginx 在ubuntu 上的启动,停止,重启
  9. python map filter reduce
  10. Ubuntu修改su和sudo密码
  11. 寻求生态保护与矿产开发平衡点 青海给出“绿色方案”
  12. 微信微调助手WeChatTweak for mac(微信多开和防撤回工具)最新版
  13. 用C写邮箱密码暴力破解器
  14. 三门问题与神奇的贝叶斯大脑
  15. 【TcaplusDB知识库】表操作—如何克隆表结构
  16. C语言基础知识复习(1)
  17. C语言 正序输出数字
  18. SecureCRT工具介绍
  19. 2010年搜索引擎的发展状况
  20. Kotlin 基础语法(《第一行代码(第三版)》第二章读书笔记)

热门文章

  1. Several frontend roundtrip diagram - synchronous and Asynchronous
  2. Cloud for Customer的主页加载逻辑
  3. Cloud for Customer的设置加载机制
  4. SAP S/4HANA的扩展字段的渲染逻辑
  5. Launch debug in SWI1 workflow
  6. Why I could not put extension fields done on CUSTOMER_H to WebUI
  7. SAP CRM呼叫中心里confirm按钮的实现逻辑
  8. ABAP, Java和JavaScript三种语言的比较
  9. OpenFOAM边界类型(终极详细介绍)
  10. 安装了vmware tools后,自动调整大小-自动适应客户机仍为灰色(终极解决方案)