I have published a feature service which has both spatial layers and non-spatial tables.
Next, I want to generate a runtime .geodatabase using GenerateGeodatabaseJob::generateGeodatabase(parameters, fileLocation).
The parameters are generated with GeodatabaseSyncTask::createDefaultGenerateGeodatabaseParameters().
The documentation of this method says
All layers from the service will be included
But also
Related tables and layers are not included.
I guess this included non-spatial tables.
To fix this, I set the GenerateLayerOption.setUseGeometry to False, and add a whereClause to select everything ("1=1").
But still, I get the following error :
Unable to create replica. Please check your parameters." : "Exporting data for layer 10 failed."
First of all, is it possible to download non-spatial tables with these methods ?
And if yes, which settings do I need to change ?
All suggestions are welcome.
PS : There is an "ugly" workaround, but I like to avoid it : convert all non-spatial tables to feature tables, by adding a shape field with 1 point
Solved! Go to Solution.
We have a test that does this. I just ran it, and it successfully generated the gdb with the table. Here is the code:
// Create default generate geodatabase parameters
auto geodatabaseSyncTask = std::unique_ptr<GeodatabaseSyncTask>(new GeodatabaseSyncTask(url_standAloneTable, this));
VERIFY2(TestObject::waitForLoadableInit(geodatabaseSyncTask.get()), "Sync task did not initialize, tests will fail");
Envelope env(-1.36572476878E7, -1.15446130221E7, 4281966740, 1.41330105216E7, SpatialReference(102100));
// A GenerateLayerOption must be specified, otherwise generateGeodatabaseAsync() throws an exception
GenerateLayerOption option;
option.setLayerId(1);
option.setWhereClause("1=1");
GenerateGeodatabaseParameters params;
params.setLayerOptions(QList<GenerateLayerOption>() << option);
// An extent must be specified, because "Geometry cannot be null in generate geodatabase parameters"
params.setExtent(env);
QString path = QDir::tempPath() + QString("/nonSpatial.geodatabase");
QPointer<GenerateGeodatabaseJob> generateGeodatabaseJob = geodatabaseSyncTask->generateGeodatabase(params, path);
Marc-
This should be possible without the workaround you suggest. See the suggestion here - GenerateGeodatabaseParameters Class | ArcGIS for Developers
"For non-spatial records, set GenerateLayerOption::useGeometry to false
and provide a GenerateLayerOption::whereClause."
Hi Luke,
I've tried that already, without success. Perhaps my whereClause is not valid ?
(but I've successfully used this in other queries).
params.setSyncModel(SyncModel::Layer);
QList<GenerateLayerOption> glo = params.layerOptions();
for (int i = 0; i < glo.size(); ++i) {
glo.setQueryOption(GenerateLayerQueryOption::All);
if (i > 10) { // the first layers do have geometry
glo.setUseGeometry(false);
glo.setWhereClause("1=1");}
}
Do I need to change something else ?
We have a test that does this. I just ran it, and it successfully generated the gdb with the table. Here is the code:
// Create default generate geodatabase parameters
auto geodatabaseSyncTask = std::unique_ptr<GeodatabaseSyncTask>(new GeodatabaseSyncTask(url_standAloneTable, this));
VERIFY2(TestObject::waitForLoadableInit(geodatabaseSyncTask.get()), "Sync task did not initialize, tests will fail");
Envelope env(-1.36572476878E7, -1.15446130221E7, 4281966740, 1.41330105216E7, SpatialReference(102100));
// A GenerateLayerOption must be specified, otherwise generateGeodatabaseAsync() throws an exception
GenerateLayerOption option;
option.setLayerId(1);
option.setWhereClause("1=1");
GenerateGeodatabaseParameters params;
params.setLayerOptions(QList<GenerateLayerOption>() << option);
// An extent must be specified, because "Geometry cannot be null in generate geodatabase parameters"
params.setExtent(env);
QString path = QDir::tempPath() + QString("/nonSpatial.geodatabase");
QPointer<GenerateGeodatabaseJob> generateGeodatabaseJob = geodatabaseSyncTask->generateGeodatabase(params, path);
Hi Luke,
I am stupid.
Of course it works, because you actually set the LayerOptions in the params (line 13 in your code snippet)
In my code, I only changed a copy, but didn't assign them back to the actual parameters.
Now it works as documented.
Just one small thing : is there a way to identify the layers in GenerateLayerOption list by name or so?
From the GeodatabaseSyncTask::featureServiceInfo(), I can get the layernames (spatial) and the tablenames (non-spatial). This allows me to calculate the number of the layer in the GenerateLayerOption list with the 1st non-spatial table. There should be an easier way to find this.
Marc- good catch.
As for your follow up question, do you have a proposal or some pseudocode of what you would like to do? From what I understand, you are able to get the IDs of the layers and tables in the service, so you should be able to loop through each of these and create appropriate layer options for each list. Let me know what you're thinking.
Luke,
(First of all : how do you add a code snippet with line numbers, like you did above ? Can't find it)
I've modified a code sample I've found somewhere on one of the many ArcGIS webpages
From the featureServiceInfo(), I can obtain layerInfos() and tableInfos(), which have ID numbers.
Here I can see that lower ID numbers are assigned to the spatial layers, followed by the non-spatial tables.
I cannot assign layerOptions here yet, because my parameters are not generated yet.
I cannot add an extra parameter in the generate(params) method, because of the lambda syntax (I'm not so familiar with this construct, but it works) => global parameter m_numLayersInService.
Like this it works, although I will not win any beauty contest with this
Marc
void myClass::downloadCopy() {
GeodatabaseSyncTask* task = new GeodatabaseSyncTask(QUrl("url_of_service"));
connect(task, &GeodatabaseSyncTask::doneLoading, this, [this](Error error) {
if (!error.isEmpty()) {
qDebug() << "Error loading GeodatabaseSyncTask:" << error.message()
}
else {
ArcGISFeatureServiceInfo featureServiceInfo = task->featureServiceInfo();
QList<IdInfo> layerInfo = featureServiceInfo.layerInfos();
m_numLayersInService = layerInfo.size();
QList<IdInfo> tableInfo = featureServiceInfo.tableInfos();
for (int i = 0; i < layerInfo.size(); ++i) {
qDebug() << "Layer" << layerInfo.name() << "ID" << layerInfo.infoId();
}
for (int i = 0; i < tabelInfo.size(); ++i) {
qDebug() << "Table" << tableInfo.name() << "ID" << tableInfo.infoId();
}
Envelope area(-20e6, -7e6, 20e6, 15e6, featureServiceInfo.spatialReference());
task->createDefaultGenerateGeodatabaseParameters(area);
}
});
// Default parameters generated ? => generate the offline geodatabase
connect(task, &GeodatabaseSyncTask::defaultGenerateGeodatabaseParametersCompleted,
this, [this](QUuid, GenerateGeodatabaseParameters parameters) {
generate(parameters);
});
task->load();
}
void myClass::generate(GenerateGeodatabaseParameters params) {
params.setSyncModel(SyncModel::Layer);QList<GenerateLayerOption> glo = params.layerOptions();
for (int i = m_numLayersInService; i < glo.size(); ++i) {
glo.setUseGeometry(false);
glo.setWhereClause("1=1");
}
params.setLayerOptions(glo);
}