ConnectivitySolver.cpp
// Copyright 2012 ESRI // // All rights reserved under the copyright laws of the United States // and applicable international laws, treaties, and conventions. // // You may freely redistribute and use this sample code, with or // without modification, provided you include the original copyright // notice and use restrictions. // // See the use restrictions. // // Copyright 2011 ESRI // // All rights reserved under the copyright laws of the United States // and applicable international laws, treaties, and conventions. // // You may freely redistribute and use this sample code, with or // without modification, provided you include the original copyright // notice and use restrictions. // // See the use restrictions at <your ArcGIS install location>/DeveloperKit10.1/userestrictions.txt. // #include "stdafx.h" #include "NameConstants.h" #include "float.h" // for FLT_MAX, etc. #include "math.h" // for HUGE_VAL #include "ConnectivitySolver.h" #include "ConnectivitySymbolizer.h" // ConnectivitySolver ///////////////////////////////////////////////////////////////////// // IConnectivitySolver STDMETHODIMP ConnectivitySolver::get_OutputLines(esriNAOutputLineType* pVal) { if (!pVal) return E_POINTER; *pVal = m_outputLineType; return S_OK; } STDMETHODIMP ConnectivitySolver::put_OutputLines(esriNAOutputLineType newVal) { m_outputLineType = newVal; m_bPersistDirty = true; return S_OK; } STDMETHODIMP ConnectivitySolver::get_OutputConnectivity(outputConnectivityType* pVal) { if (!pVal) return E_POINTER; *pVal = m_outputConnectivityType; return S_OK; } STDMETHODIMP ConnectivitySolver::put_OutputConnectivity(outputConnectivityType newVal) { m_outputConnectivityType = newVal; m_bPersistDirty = true; return S_OK; } ///////////////////////////////////////////////////////////////////// // INASolver STDMETHODIMP ConnectivitySolver::get_Name(BSTR* pName) { if (!pName) return E_POINTER; // This name is locale/language independent and should not be translated. *pName = ::SysAllocString(CS_NAME); return S_OK; } STDMETHODIMP ConnectivitySolver::get_DisplayName(BSTR* pName) { if (!pName) return E_POINTER; // This name should be translated and would typically come from // a string resource. *pName = ::SysAllocString(CS_DISPLAY_NAME); return S_OK; } STDMETHODIMP ConnectivitySolver::get_ClassDefinitions(INamedSet** ppDefinitions) { if (!ppDefinitions) return E_POINTER; *ppDefinitions = 0; // get_ClassDefinitions() will return the default NAClasses that this solver uses. // We will define a NAClass for "Seed Points", "Barriers", and "Lines". // Barriers are just like the other NA Solvers' Barriers. // Lines are the sets of connected/disconnected lines that this solver discovers. // Lines are akin to the Routes output from a Route Solver. // Seed Points are the origins of traversal for this solver. ISpatialReferencePtr ipUnkSR(CLSID_UnknownCoordinateSystem); HRESULT hr; if (FAILED(hr = BuildClassDefinitions(ipUnkSR, ppDefinitions, 0))) return AtlReportError(GetObjectCLSID(), _T("Failed to create class definitions."), IID_INASolver, hr); return S_OK; } STDMETHODIMP ConnectivitySolver::get_CanUseHierarchy(VARIANT_BOOL* pCanUseHierarchy) { if (!pCanUseHierarchy) return E_POINTER; // This solver does not make use of hierarchies *pCanUseHierarchy = VARIANT_FALSE; return S_OK; } STDMETHODIMP ConnectivitySolver::get_CanAccumulateAttributes(VARIANT_BOOL* pCanAccumulateAttrs) { if (!pCanAccumulateAttrs) return E_POINTER; // This solver does not support attribute accumulation *pCanAccumulateAttrs = VARIANT_FALSE; return S_OK; } STDMETHODIMP ConnectivitySolver::get_Properties(IPropertySet** ppPropSet) { if (!ppPropSet) return E_POINTER; // The use of the property set has been deprecated at 9.2. // Clients should use the accessors and mutators on the solver interfaces. *ppPropSet = 0; return E_NOTIMPL; } STDMETHODIMP ConnectivitySolver::CreateLayer(INAContext* pContext, INALayer** ppLayer) { if (!ppLayer) return E_POINTER; *ppLayer = 0; // This is an appropriate place to check if the user is licensed to run // your solver and fail with "E_NOT_LICENSED" or similar. // Create our custom symbolizer and use it to create the NALayer. // NOTE: we are assuming here that there is only one symbolizer to // consider. The ArcGIS Network Analyst extension framework and ESRI solvers support the notion // that there can be many symbolizers in CATID_NetworkAnalystSymbolizer. // One can iterate the objects in this category and call the Applies() // method to see if the symbolizer should be used for a particular solver/context. // There is also a get_Priority() method that is used to determine which // to use if many Apply. INASymbolizerPtr ipNASymbolizer(__uuidof(ConnectivitySymbolizer)); return ipNASymbolizer->CreateLayer(pContext, ppLayer); } STDMETHODIMP ConnectivitySolver::UpdateLayer(INALayer* pLayer, VARIANT_BOOL* pLayerUpdated) { if (!pLayer || !pLayerUpdated) return E_POINTER; // This method is called after Solve() and gives us a chance to react to the results of // the Solve and change the layer. For example, the Service Area solver updates // its layer renderers after Solve has been called to adjust for unique values. // We will not need to update our layer, since our renderers will remain the same throughout *pLayerUpdated = VARIANT_FALSE; return S_OK; } STDMETHODIMP ConnectivitySolver::Solve(INAContext* pNAContext, IGPMessages* pMessages, ITrackCancel* pTrackCancel, VARIANT_BOOL* pIsPartialSolution) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Solve is the method that is called to perform the actual network analysis. The solver component // should be in a valid state before this method should be called. For example, within the ArcMap // application, ArcGIS Network Analyst extension performs certain validation checks on the solver and its associated output // before enabling the Solve command (on the Network Analyst toolbar) for any given solver. // This validation includes the following status checks: // 1) check that the solver has created a valid context // 2) check that the solver has created a valid NALayer // 3) check that the solver's associated NAClass feature classes currently have valid cardinalities // (this last concept is discussed in the BuildClassDefinitions method of this custom solver) // Once the solver component has produced a valid state for appropriate processing (such as defined above), // this method should be available to call // NOTE: for consistency within custom applications, similar validation checks should also be implemented // before calling the Solve method on any solver HRESULT hr; // Check for null parameter variables (the track cancel variable is typically considered optional) if (!pNAContext || !pMessages) return E_POINTER; // The partialSolution variable is used to indicate to the caller of this method whether or not we were only able to // find a partial solution to the problem. We initialize this variable to false and set it to true only in certain // conditional cases (e.g., some stops/points are unreachable, a seed point is unlocated, etc.) *pIsPartialSolution = VARIANT_FALSE; // NOTE: this is an appropriate place to check if the user is licensed to run // your solver and fail with "E_NOT_LICENSED" or similar. // Clear the GP messages if (FAILED(hr = pMessages->Clear())) return hr; // Validate the context (i.e., make sure that it is bound to a network dataset) INetworkDatasetPtr ipNetworkDataset; if (FAILED(hr = pNAContext->get_NetworkDataset(&ipNetworkDataset))) return hr; if (!ipNetworkDataset) return AtlReportError(GetObjectCLSID(), _T("Context does not have a valid network dataset."), IID_INASolver); // NOTE: this is also a good place to perform any additional necessary validation, such as // synchronizing the attribute names set on your solver with those of the context's network dataset // Check for a Step Progressor on the track cancel parameter variable. // This can be used to indicate progress and output messages to the client throughout the solve CancelTrackerHelper cancelTrackerHelper; IStepProgressorPtr ipStepProgressor; if (pTrackCancel) { IProgressorPtr ipProgressor; if (FAILED(hr = pTrackCancel->get_Progressor(&ipProgressor))) return hr; ipStepProgressor = ipProgressor; // We use the cancel tracker helper object to disassociate and reassociate the cancel tracker from the progressor // during and after the Solve, respectively. This prevents calls to ITrackCancel::Continue from stepping our progressor cancelTrackerHelper.ManageTrackCancel(pTrackCancel); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Reset each NAClass to an appropriate state (as necessary) before proceeding // This is typically done in order to: // 1) remove any features that were previously created in our output NAClasses from previous solves // 2) check the input NAClasses for output fields and reset these field values as necessary // In our case, we do not have any input NAClasses with output fields, and we only have one output feature class, // so we can simply clear any features currently present in the "LineData" NAClass. // NOTE: if you have multiple input/output NAClasses to clean up, you can simply loop through all NAClasses, // get their NAClassDefinition, check whether or not each is an input/output class, and reset it accordingly INamedSetPtr ipNAClasses; if (FAILED(hr = pNAContext->get_NAClasses(&ipNAClasses))) return hr; IUnknownPtr ipUnk; if (FAILED(hr = ipNAClasses->get_ItemByName(CComBSTR(CS_LINES_NAME), &ipUnk))) return hr; INAClassPtr ipLinesNAClass(ipUnk); if (FAILED(hr = ipLinesNAClass->DeleteAllRows())) // remove any features that might have been created on previous solves return hr; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Setup the Network Forward Star for traversal // Create a Forward Star object from the INetworkQuery interface of the network dataset INetworkQueryPtr ipNetworkQuery(ipNetworkDataset); INetworkForwardStarPtr ipNetworkForwardStar; if (FAILED(hr = ipNetworkQuery->CreateForwardStar(&ipNetworkForwardStar))) return hr; // QI the Forward Star to INetworkForwardStarEx // This interface can be used to setup necessary traversal constraints on the Forward Star before proceeding // This typically includes: // 1) setting the traversal direction (through INetworkForwardStarSetup::IsForwardTraversal) // 2) setting any restrictions on the forward star (e.g., oneway restrictions, restricted turns, etc.) // 3) setting the U-turn policy (through INetworkForwardStarSetup::Backtrack) // 4) setting the hierarchy (through INetworkForwardStarSetup::HierarchyAttribute) // 5) setting up traversable/non-traversable elements (in our case, we will be setting up barriers as non-traversable) INetworkForwardStarExPtr ipNetworkForwardStarEx(ipNetworkForwardStar); // Get the "Barriers" NAClass table (we need the NALocation objects from this NAClass to push barriers into the Forward Star) ITablePtr ipBarriersTable; if (FAILED(hr = GetNAClassTable(pNAContext, CComBSTR(CS_BARRIERS_NAME), &ipBarriersTable))) return hr; // Load the barriers if (FAILED(hr = LoadBarriers(ipBarriersTable, ipNetworkQuery, ipNetworkForwardStarEx))) return hr; // Create a Forward Star Adjacencies object (we need this object to hold traversal queries carried out on the Forward Star) INetworkForwardStarAdjacenciesPtr ipNetworkForwardStarAdjacencies; if (FAILED(hr = ipNetworkQuery->CreateForwardStarAdjacencies(&ipNetworkForwardStarAdjacencies))) return hr; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Begin traversal // Determine the maximum number of edge elements in the network dataset and initialize data structures long maxEdgeEID, totalEdgeEIDs, totalConnectedEdgeEIDs, totalDisconnectedEdgeEIDs; if (FAILED(hr = ipNetworkQuery->get_MaxEID(esriNETEdge, &maxEdgeEID))) return hr; if (FAILED(hr = ipNetworkQuery->get_ElementCount(esriNETEdge, &totalEdgeEIDs))) return hr; // We must divide this value in half because get_ElementCount returns two counts for each unique edge EID: one for each direction // Since this is an undirected search, we are not concerned with edge direction totalEdgeEIDs /= 2; // Edge connectivity bitmap connectivity_bitmap edgeEIDIsConnected; edgeEIDIsConnected.resize(maxEdgeEID + 1); // initialize bitmap size (all are automatically set to false) // Stack for holding junction EIDs junction_stack junctionStack; // Hashtable for holding segmentRecords segment_hash segmentRecordHashTable; // Get the "Seed Points" NAClass table (we need the NALocation objects from this NAClass as the starting points for Forward Star // traversals) ITablePtr ipSeedPointsTable; if (FAILED(hr = GetNAClassTable(pNAContext, CComBSTR(CS_SEED_POINTS_NAME), &ipSeedPointsTable))) return hr; // Setup our progressor based on the number of seed points if (ipStepProgressor) { long numberOfSeedPoints; if (FAILED(hr = ipSeedPointsTable->RowCount(0, &numberOfSeedPoints))) return hr; // Step progressor range = 0 through numberOfSeedPoints if (FAILED(hr = ipStepProgressor->put_MinRange(0))) // 0 through... return hr; if (FAILED(hr = ipStepProgressor->put_MaxRange(numberOfSeedPoints))) // numberOfSeedPoints... return hr; if (FAILED(hr = ipStepProgressor->put_StepValue(1))) // incremented in step intervals of 1... return hr; if (FAILED(hr = ipStepProgressor->put_Position(0))) // and starting at step 0 return hr; // Show the progressor if (FAILED(hr = ipStepProgressor->Show())) return hr; } // Get a cursor on the seed points table to loop through each row ICursorPtr ipCursor; if (FAILED(hr = ipSeedPointsTable->Search(0, VARIANT_TRUE, &ipCursor))) return hr; // Create variables for looping through the cursor and traversing the network IRowPtr ipRow; INALocationObjectPtr ipNALocationObject; INALocationPtr ipNALocation(CLSID_NALocation); IEnumNetworkElementPtr ipEnumNetworkElement; INetworkEdgePtr ipCurrentEdge; INetworkJunctionPtr ipCurrentJunction; INetworkElementPtr ipElement; ipNetworkQuery->CreateNetworkElement(esriNETEdge, &ipElement); ipCurrentEdge = ipElement; ipNetworkQuery->CreateNetworkElement(esriNETJunction, &ipElement); ipCurrentJunction = ipElement; long sourceID, sourceOID, currentEdgeEID, currentJunctionEID, adjacentEdgeCount; double posAlong, fromPosition, toPosition; esriNetworkElementType elementType; // Setup a message on our step progressor indicating that we are traversing the network if (ipStepProgressor) ipStepProgressor->put_Message(CComBSTR(L"Computing connectivity from seed point(s)")); // add more specific information here if appropriate // This begins our main network traversal loop // Loop through the cursor getting the NALocation of each NALocationObject in the seed points table, // and traverse the network from each seed NALocation, setting flags in our connectivity bitmap as appropriate totalConnectedEdgeEIDs = 0; VARIANT_BOOL keepGoing, isLocated, isRestricted; while (ipCursor->NextRow(&ipRow) == S_OK) { ipNALocationObject = ipRow; if (!ipNALocationObject) // we only want valid NALocationObjects { // If this seed point is an invalid NALocationObject, we will only be able to find a partial solution *pIsPartialSolution = VARIANT_TRUE; continue; } if (FAILED(hr = ipNALocationObject->QueryNALocation(ipNALocation))) return hr; // Once we have the NALocation, we need to check if it is actually located within the network dataset isLocated = VARIANT_FALSE; if (ipNALocation) { if (FAILED(hr = ipNALocation->get_IsLocated(&isLocated))) return hr; } // We are only concerned with located seed point NALocations if (!isLocated) { // If this seed point is unlocated, we will only be able to find a partial solution *pIsPartialSolution = VARIANT_TRUE; } else { // Get the SourceID for the NALocation if (FAILED(hr = ipNALocation->get_SourceID(&sourceID))) return hr; // Get the SourceOID for the NALocation if (FAILED(hr = ipNALocation->get_SourceOID(&sourceOID))) return hr; // Get the PosAlong for the NALocation if (FAILED(hr = ipNALocation->get_SourcePosition(&posAlong))) return hr; // Once we have a located NALocation, we query the network to obtain its associated network elements if (FAILED(hr = ipNetworkQuery->get_ElementsByOID(sourceID, sourceOID, &ipEnumNetworkElement))) return hr; // We must loop through the returned elements, looking for an appropriate starting point ipEnumNetworkElement->Reset(); while (ipEnumNetworkElement->Next(&ipElement) == S_OK) { // We must first check that the network element is traversable // We can only begin a traversal from a traversable element if (FAILED(hr = ipNetworkForwardStarEx->get_IsRestricted(ipElement, &isRestricted))) return hr; if (isRestricted) continue; // We must then check the returned element type ipElement->get_ElementType(&elementType); // If the element is a junction, then it is the starting point of traversal // We simply add its EID to the stack and break out of the enumerating loop if (elementType == esriNETJunction) { if (FAILED(hr = ipElement->get_EID(¤tJunctionEID))) return hr; junctionStack.push(currentJunctionEID); break; // exit the containing while loop } // If the element is an edge, then we must check the fromPosition and toPosition to be certain it holds an appropriate starting point if (elementType == esriNETEdge) { INetworkEdgePtr ipEdge(ipElement); if (FAILED(hr = ipEdge->QueryPositions(&fromPosition, &toPosition))) return hr; if (fromPosition <= posAlong && posAlong <= toPosition) { // Our NALocation lies along this edge element // We will start our traversal from the FromJunction of this edge if (FAILED(hr = ipEdge->QueryJunctions(ipCurrentJunction, 0))) return hr; // We simply add its EID to the stack and break out of the enumerating loop if (FAILED(hr = ipCurrentJunction->get_EID(¤tJunctionEID))) return hr; junctionStack.push(currentJunctionEID); break; // exit the containing while loop } } } // Continue traversing the network while the stack has remaining junction EIDs in it while (junctionStack.size() > 0) { // Remove the next junction EID from the top of the stack currentJunctionEID = junctionStack.top(); junctionStack.pop(); // Query for this junction using its EID if (FAILED(hr = ipNetworkForwardStarEx->QueryJunction(currentJunctionEID, ipCurrentJunction))) return hr; // Query adjacencies from the current junction if (FAILED(hr = ipNetworkForwardStarEx->QueryAdjacencies(ipCurrentJunction, 0, 0, ipNetworkForwardStarAdjacencies))) return hr; // Get the adjacent edge count if (FAILED(hr = ipNetworkForwardStarAdjacencies->get_Count(&adjacentEdgeCount))) return hr; // Loop through all adjacent edges setting their connectivity to true (if they haven't already been traversed) for (long i = 0; i < adjacentEdgeCount; i++) { if (FAILED(hr = ipNetworkForwardStarAdjacencies->QueryEdge(i, ipCurrentEdge, &fromPosition, &toPosition))) return hr; if (FAILED(hr = ipCurrentEdge->get_EID(¤tEdgeEID))) return hr; // Check to see if we have traversed this edge before if (!edgeEIDIsConnected[currentEdgeEID]) { // If not, set its connectivity to true edgeEIDIsConnected[currentEdgeEID] = true; // Add this edge element's ToJunction to the junctionStack for further traversal if (FAILED(hr = ipCurrentEdge->QueryJunctions(0, ipCurrentJunction))) return hr; if (FAILED(hr = ipCurrentJunction->get_EID(¤tJunctionEID))) return hr; junctionStack.push(currentJunctionEID); // Increment the total number of connected edges totalConnectedEdgeEIDs++; } } } } // Step the progressor before continuing to the next seed point if (ipStepProgressor) ipStepProgressor->Step(); // Check to see if the user wishes to continue or cancel the solve (i.e., check whether or not the user has hit the ESC key to stop processing) if (pTrackCancel) { if (FAILED(hr = pTrackCancel->Continue(&keepGoing))) return hr; if (keepGoing == VARIANT_FALSE) { // The user wishes to cancel the solve // The cancel tracker helper will automatically hide the progressor and reassociate it with the track cancel object // (this is done implicitly from its deconstructor) return E_ABORT; } } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Write output // Now that we have completed our traversal of the network from the seed points, we must output the connected/disconnected edges // to the "LineData" NAClass // We already know the total number of connected edges, so we must figure out the remaining // number of disconnected edges, if any totalDisconnectedEdgeEIDs = totalEdgeEIDs - totalConnectedEdgeEIDs; // Check to see what kind and how many features we will be outputting to the Lines NAClass long numberOfOutputSteps = (m_outputConnectivityType == outputDisconnectedLines) ? totalDisconnectedEdgeEIDs : totalConnectedEdgeEIDs; // Check to see if we actually have any features to output if(numberOfOutputSteps == 0) { // Since we have nothing to output, we report the connectivity results to the user and return if(m_outputConnectivityType == outputDisconnectedLines) pMessages->AddMessage(CComBSTR(L"The network is fully connected to the seed point(s).")); else pMessages->AddMessage(CComBSTR(L"The network is fully disconnected from the seed point(s).")); // The cancel tracker helper will automatically hide the progressor and reassociate it with the track cancel object // (this is done implicitly from its deconstructor) return S_OK; } // If we reach this point, we have some connected/disconnected features to output to the Lines NAClass // Reset the progressor based on the number of features that we must output if (ipStepProgressor) { // Step progressor range = 0 through numberOfOutputSteps if (FAILED(hr = ipStepProgressor->put_MinRange(0))) return hr; if (FAILED(hr = ipStepProgressor->put_MaxRange(numberOfOutputSteps))) return hr; if (FAILED(hr = ipStepProgressor->put_StepValue(1))) return hr; if (FAILED(hr = ipStepProgressor->put_Position(0))) return hr; } // Create an output type check to determine which edges to output based on their connectivity value within the bitmap bool outputTypeCheck = (m_outputConnectivityType == outputDisconnectedLines) ? false : true; // Get the "LineData" NAClass feature class IFeatureClassPtr ipLinesFC(ipLinesNAClass); // Create an insert cursor and feature buffer from the "LineData" feature class to be used to write connected/disconnected edges IFeatureCursorPtr ipFeatureCursor; if (FAILED(hr = ipLinesFC->Insert(VARIANT_TRUE, &ipFeatureCursor))) return hr; IFeatureBufferPtr ipFeatureBuffer; if (FAILED(hr = ipLinesFC->CreateFeatureBuffer(&ipFeatureBuffer))) return hr; // Query for the appropriate field index values in the "LineData" feature class long sourceIDFieldIndex, sourceOIDFieldIndex, fromPositionFieldIndex, toPositionFieldIndex; if (FAILED(hr = ipLinesFC->FindField(CComBSTR(CS_FIELD_SOURCE_ID), &sourceIDFieldIndex))) return hr; if (FAILED(hr = ipLinesFC->FindField(CComBSTR(CS_FIELD_SOURCE_OID), &sourceOIDFieldIndex))) return hr; if (FAILED(hr = ipLinesFC->FindField(CComBSTR(CS_FIELD_FROM_POS), &fromPositionFieldIndex))) return hr; if (FAILED(hr = ipLinesFC->FindField(CComBSTR(CS_FIELD_TO_POS), &toPositionFieldIndex))) return hr; // Setup a message on our step progressor indicating that we are outputting connected/disconnected feature information if (ipStepProgressor) ipStepProgressor->put_Message(CComBSTR(L"Writing output features")); // add more specific information here if appropriate long sourceCount; if (FAILED(hr = ipNetworkDataset->get_SourceCount(&sourceCount))) return hr; // Loop through each network source checking for edge sources INetworkSourcePtr ipNetworkSource; IFeatureClassPtr ipNetworkSourceFC; IFeatureClassContainerPtr ipFeatureClassContainer(ipNetworkDataset); CComBSTR sourceName; CComVariant featureID(0); long lineFeatureCount = 0; for (long i = 0; i < sourceCount; i++) { // Get the network source at this index if (FAILED(hr = ipNetworkDataset->get_Source(i, &ipNetworkSource))) return hr; // Get the element type of this network source if (FAILED(hr = ipNetworkSource->get_ElementType(&elementType))) return hr; // If the network source is not an edge source, then continue if (elementType != esriNETEdge) continue; // Get the SourceID for this source if (FAILED(hr = ipNetworkSource->get_ID(&sourceID))) return hr; // Get the source feature class if (FAILED(hr = ipNetworkSource->get_Name(&sourceName))) return hr; if (FAILED(hr = ipFeatureClassContainer->get_ClassByName(sourceName, &ipNetworkSourceFC))) return hr; if (!ipNetworkSourceFC) // Some SDC sources do not represent valid feature classes. We must check for this and continue, as appropriate continue; // Query for an enumerator of all edge elements from this source if (FAILED(hr = ipNetworkQuery->get_ElementsForSource(sourceID, &ipEnumNetworkElement))) return hr; // Loop through all returned edge elements, checking whether to use them for connected/disconnected output while (ipEnumNetworkElement->Next(&ipElement) == S_OK) { if (FAILED(hr = ipElement->get_EID(¤tEdgeEID))) return hr; // Check edge element for output as connected/disconnected if (edgeEIDIsConnected[currentEdgeEID] == outputTypeCheck) { // We will use this edge for feature output to the "LineData" NAClass ipCurrentEdge = ipElement; // Get the SourceOID of the edge if (FAILED(hr = ipCurrentEdge->get_OID(&sourceOID))) return hr; // Get the from/to positions of the edge if (FAILED(hr = ipCurrentEdge->QueryPositions(&fromPosition, &toPosition))) return hr; // Check for whether or not we are outputting geometry here if (m_outputLineType == esriNAOutputLineTrueShape) { // See if we have already entered this OID in the hash table; if not, increment the line feature count if(segmentRecordHashTable.count(sourceOID) == 0) lineFeatureCount++; // Create a new segment record and store it as an entry in the hash table using the SourceOID as its key value segmentRecord currentSegmentRecord(fromPosition, toPosition); segmentRecordHashTable.insert(segment_entry(sourceOID, currentSegmentRecord)); // Check the number of unique OID values in the hash table // Every c_featureRetrievalInterval OIDs, we will grab features from the source feature class and create new "LineData" features // This is more efficient than sequentially calling GetFeature(...) for each individual feature if (lineFeatureCount == c_featureRetrievalInterval) { if (FAILED(hr = OutputLineFeatures(sourceID, lineFeatureCount, segmentRecordHashTable, ipNetworkSourceFC, ipFeatureCursor, ipFeatureBuffer))) return hr; // Reset the cumulative line feature count to zero lineFeatureCount = 0; } } else { // Otherwise, we can simply store the values on the feature buffer and insert the feature into the insert cursor if (FAILED(hr = ipFeatureBuffer->put_Value(sourceIDFieldIndex, CComVariant(sourceID)))) return hr; if (FAILED(hr = ipFeatureBuffer->put_Value(sourceOIDFieldIndex, CComVariant(sourceOID)))) return hr; if (FAILED(hr = ipFeatureBuffer->put_Value(fromPositionFieldIndex, CComVariant(fromPosition)))) return hr; if (FAILED(hr = ipFeatureBuffer->put_Value(toPositionFieldIndex, CComVariant(toPosition)))) return hr; // Insert the feature buffer in the insert cursor if (FAILED(hr = ipFeatureCursor->InsertFeature(ipFeatureBuffer, &featureID))) return hr; } // Step the progressor before continuing if (ipStepProgressor) ipStepProgressor->Step(); } // Check to see if the user wishes to continue or cancel the solve (i.e., check whether or not the user has hit the ESC key to stop processing) if (pTrackCancel) { if (FAILED(hr = pTrackCancel->Continue(&keepGoing))) return hr; if (keepGoing == VARIANT_FALSE) { // The user wishes to cancel the solve // The cancel tracker helper will automatically hide the progressor and reassociate it with the track cancel object // (this is done implicitly from its deconstructor) return E_ABORT; } } } // Make sure there are no remaining features to be output to the NAClass if (m_outputLineType == esriNAOutputLineTrueShape && lineFeatureCount > 0) { if (FAILED(hr = OutputLineFeatures(sourceID, lineFeatureCount, segmentRecordHashTable, ipNetworkSourceFC, ipFeatureCursor, ipFeatureBuffer))) return hr; // Reset the cumulative line feature count to zero lineFeatureCount = 0; } } CString formatString; CString msgString; // Report the connectivity results to the user and return if (m_outputConnectivityType == outputDisconnectedLines) { formatString = _T("The network has %d edges disconnected from the seed point(s)."); msgString.Format(formatString, numberOfOutputSteps); } else { formatString = _T("The network has %d edges connected to the seed point(s)."); msgString.Format(formatString, numberOfOutputSteps); } pMessages->AddMessage(CComBSTR(msgString)); // The cancel tracker helper will automatically hide the progressor and reassociate it with the track cancel object // (this is done implicitly from its deconstructor) return S_OK; } STDMETHODIMP ConnectivitySolver::CreateContext(IDENetworkDataset* pNetwork, BSTR contextName, INAContext** ppNAContext) { if (!pNetwork || !ppNAContext) return E_POINTER; if (!contextName || !::wcslen(contextName)) return E_INVALIDARG; *ppNAContext = 0; HRESULT hr; // CreateContext() is called by the command that creates the solver and // initializes it. Below we'll: // // - create the class definitions // - set up defaults // // After this method is called, this class should be prepared to have // its solve method called. // This is an appropriate place to check if the user is licensed to run // your solver and fail with "E_NOT_LICENSED" or similar. // Get the NDS SpatialRef, that will be the same for the context as well as all spatial NAClasses IDEGeoDatasetPtr ipDEGeoDataset(pNetwork); if (!ipDEGeoDataset) return E_INVALIDARG; ISpatialReferencePtr ipNAContextSR; if (FAILED(hr = ipDEGeoDataset->get_SpatialReference(&ipNAContextSR))) return hr; IUnknownPtr ipUnknown; INamedSetPtr ipNAClassDefinitions; INAClassDefinitionPtr ipSeedPointsClassDef; INAClassDefinitionPtr ipBarriersClassDef; INAClassDefinitionPtr ipLinesClassDef; // Build the class definitions if (FAILED(hr = BuildClassDefinitions(ipNAContextSR, &ipNAClassDefinitions, pNetwork))) return hr; ipNAClassDefinitions->get_ItemByName(CComBSTR(CS_SEED_POINTS_NAME), &ipUnknown); ipSeedPointsClassDef = ipUnknown; ipNAClassDefinitions->get_ItemByName(CComBSTR(CS_BARRIERS_NAME), &ipUnknown); ipBarriersClassDef = ipUnknown; ipNAClassDefinitions->get_ItemByName(CComBSTR(CS_LINES_NAME), &ipUnknown); ipLinesClassDef = ipUnknown; // Create a context and initialize it INAContextPtr ipNAContext(CLSID_NAContext); INAContextEditPtr ipNAContextEdit(ipNAContext); if (FAILED(hr = ipNAContextEdit->Initialize(contextName, pNetwork))) return hr; ipNAContextEdit->putref_Solver(static_cast<INASolver*>(this)); // Create NAClasses for each of our class definitions INamedSetPtr ipNAClasses; INAClassPtr ipNAClass; ipNAContext->get_NAClasses(&ipNAClasses); if (FAILED(hr = ipNAContextEdit->CreateAnalysisClass(ipSeedPointsClassDef, &ipNAClass))) return hr; ipNAClasses->Add(CComBSTR(CS_SEED_POINTS_NAME), (IUnknownPtr)ipNAClass); if (FAILED(hr = ipNAContextEdit->CreateAnalysisClass(ipBarriersClassDef, &ipNAClass))) return hr; ipNAClasses->Add(CComBSTR(CS_BARRIERS_NAME), (IUnknownPtr)ipNAClass); if (FAILED(hr = ipNAContextEdit->CreateAnalysisClass(ipLinesClassDef, &ipNAClass))) return hr; ipNAClasses->Add(CComBSTR(CS_LINES_NAME), (IUnknownPtr)ipNAClass); // NOTE: this is an appropriate place to set up any hierarchy defaults if your // solver supports using a hierarchy attribute (this solver does not). This is // also an appropriate place to set the default impedance attribute if you // had one. // Set up our solver defaults m_outputConnectivityType = outputDisconnectedLines; m_outputLineType = esriNAOutputLineTrueShape; // Agents setup // NOTE: this is an appropriate place to find and attach any agents used by this solver. // For example, the route solver would attach the directions agent. // Initialize the default field mappings // NOTE: this is an appropriate place to set up any default field mappings to be used // Return the context once it has been fully created and initialized *ppNAContext = ipNAContext; ipNAContext->AddRef(); return S_OK; } STDMETHODIMP ConnectivitySolver::UpdateContext(INAContext* pNAContext, IDENetworkDataset* pNetwork, IGPMessages* pMessages) { // UpdateContext() is a method used to update the context based on any changes that may have been made to the // solver settings. This typically includes changes to the set of accumulation attribute names, etc., which can // be set as fields in the context's NAClass schemas // We will not need to update our context, since we do not support any changes necessary to use this method return S_OK; } STDMETHODIMP ConnectivitySolver::Bind(INAContext* pContext, IDENetworkDataset* pNetwork, IGPMessages* pMessages) { // Bind() is a method used to re-associate the solver with a given network dataset and its schema. Calling Bind() // on the solver re-attaches the solver to the NAContext based on the current network dataset settings. // This is typically used to update the solver and its context based on changes in the network dataset's available // restrictions, hierarchy attributes, cost attributes, etc. // We will not need to bind our solver, since we do not support any changes necessary to use this method return S_OK; } ///////////////////////////////////////////////////////////////////// // INASolverSettings STDMETHODIMP ConnectivitySolver::get_AccumulateAttributeNames(IStringArray** ppAttributeNames) { return E_NOTIMPL; // This solver does not support attribute accumulation } STDMETHODIMP ConnectivitySolver::putref_AccumulateAttributeNames(IStringArray* pAttributeNames) { return E_NOTIMPL; // This solver does not support attribute accumulation } STDMETHODIMP ConnectivitySolver::put_ImpedanceAttributeName(BSTR attributeName) { return E_NOTIMPL; // This solver does not support impedance attributes } STDMETHODIMP ConnectivitySolver::get_ImpedanceAttributeName(BSTR* pAttributeName) { return E_NOTIMPL; // This solver does not support impedance attributes } STDMETHODIMP ConnectivitySolver::put_IgnoreInvalidLocations(VARIANT_BOOL ignoreInvalidLocations) { return E_NOTIMPL; } STDMETHODIMP ConnectivitySolver::get_IgnoreInvalidLocations(VARIANT_BOOL* pIgnoreInvalidLocations) { return E_NOTIMPL; } STDMETHODIMP ConnectivitySolver::get_RestrictionAttributeNames(IStringArray** ppAttributeName) { return E_NOTIMPL; // This solver does not support restriction attributes } STDMETHODIMP ConnectivitySolver::putref_RestrictionAttributeNames(IStringArray* pAttributeName) { return E_NOTIMPL; // This solver does not support restriction attributes } STDMETHODIMP ConnectivitySolver::put_RestrictUTurns(esriNetworkForwardStarBacktrack backtrack) { return E_NOTIMPL; // This solver does not support U-turn policies } STDMETHODIMP ConnectivitySolver::get_RestrictUTurns(esriNetworkForwardStarBacktrack* pBacktrack) { return E_NOTIMPL; // This solver does not support U-turn policies } STDMETHODIMP ConnectivitySolver::put_UseHierarchy(VARIANT_BOOL useHierarchy) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::get_UseHierarchy(VARIANT_BOOL* pUseHierarchy) { if (!pUseHierarchy) return E_POINTER; // This solver does not support hierarchy attributes *pUseHierarchy = VARIANT_FALSE; return S_OK; } STDMETHODIMP ConnectivitySolver::put_HierarchyAttributeName(BSTR attributeName) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::get_HierarchyAttributeName(BSTR* pattributeName) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::put_HierarchyLevelCount(long Count) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::get_HierarchyLevelCount(long* pCount) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::put_MaxValueForHierarchy(long level, long value) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::get_MaxValueForHierarchy(long level, long* pValue) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::put_NumTransitionToHierarchy(long toLevel, long value) { return E_NOTIMPL; // This solver does not support hierarchy attributes } STDMETHODIMP ConnectivitySolver::get_NumTransitionToHierarchy(long toLevel, long* pValue) { return E_NOTIMPL; // This solver does not support hierarchy attributes } ///////////////////////////////////////////////////////////////////// // IPersistStream STDMETHODIMP ConnectivitySolver::IsDirty() { return (m_bPersistDirty ? S_OK : S_FALSE); } STDMETHODIMP ConnectivitySolver::Load(IStream* pStm) { if (!pStm) return E_POINTER; ULONG numBytes; HRESULT hr; // We need to check the saved version number long savedVersion; if (FAILED(hr = pStm->Read(&savedVersion, sizeof(savedVersion), &numBytes))) return hr; // We only support versions less than or equal to the current c_version if (savedVersion > c_version || savedVersion <= 0) return E_FAIL; // We need to read our persisted solver settings if (FAILED(hr = pStm->Read(&m_outputConnectivityType, sizeof(m_outputConnectivityType), &numBytes))) return hr; if (FAILED(hr = pStm->Read(&m_outputLineType, sizeof(m_outputLineType), &numBytes))) return hr; m_bPersistDirty = false; return S_OK; } STDMETHODIMP ConnectivitySolver::Save(IStream* pStm, BOOL fClearDirty) { if (fClearDirty) m_bPersistDirty = false; ULONG numBytes; HRESULT hr; // We need to persist the c_version number if (FAILED(hr = pStm->Write(&c_version, sizeof(c_version), &numBytes))) return hr; // We need to persist our solver settings if (FAILED(hr = pStm->Write(&m_outputConnectivityType, sizeof(m_outputConnectivityType), &numBytes))) return hr; if (FAILED(hr = pStm->Write(&m_outputLineType, sizeof(m_outputLineType), &numBytes))) return hr; return S_OK; } STDMETHODIMP ConnectivitySolver::GetSizeMax(_ULARGE_INTEGER* pCbSize) { if (!pCbSize) return E_POINTER; pCbSize->HighPart = 0; pCbSize->LowPart = sizeof(short); return S_OK; } STDMETHODIMP ConnectivitySolver::GetClassID(CLSID *pClassID) { if (!pClassID) return E_POINTER; *pClassID = __uuidof(ConnectivitySolver); return S_OK; } ////////////////////////////////////////// // private methods HRESULT ConnectivitySolver::BuildClassDefinitions(ISpatialReference* pSpatialRef, INamedSet** ppDefinitions, IDENetworkDataset* pDENDS) { if (!pSpatialRef || !ppDefinitions) return E_POINTER; // This function creates the class definitions for the connectivity solver. // Recall, class definitions are in-memory feature classes that store the // inputs and outputs for a solver. This solver's class definitions are: // Seed Points (input) Seed points determine the start locations // - OID of the search // - Shape // - Name // - (NALocation fields) // Barriers (input) Barriers restrict network edges from being // - OID traversable // - Shape // - Name // - (NALocation fields) // Lines (output) Connected or Disconnected lines depending // - OID on the OutputConnectivity solver option // - Shape // - SourceID // - SourceOID // - FromPos // - ToPos HRESULT hr; // Create the class definitions named set and the variables needed to properly instantiate them INamedSetPtr ipClassDefinitions(CLSID_NamedSet); INAClassDefinitionPtr ipClassDef; INAClassDefinitionEditPtr ipClassDefEdit; IUIDPtr ipIUID; IFieldsPtr ipFields; IFieldsEditPtr ipFieldsEdit; IFieldPtr ipField; IFieldEditPtr ipFieldEdit; ////////////////////////////////////////////////////////// // Seed Points class definition ipClassDef.CreateInstance(CLSID_NAClassDefinition); ipClassDefEdit = ipClassDef; ipIUID.CreateInstance(CLSID_UID); if (FAILED(hr = ipIUID->put_Value(CComVariant(L"esriNetworkAnalyst.NALocationFeature")))) return hr; ipClassDefEdit->putref_ClassCLSID(ipIUID); // Create the fields for the class definition ipFields.CreateInstance(CLSID_Fields); ipFieldsEdit = ipFields; // Create and add an OID field ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_OID)); ipFieldEdit->put_Type(esriFieldTypeOID); ipFieldsEdit->AddField(ipFieldEdit); // Create and add a Shape field ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; { IGeometryDefEditPtr ipGeoDef(CLSID_GeometryDef); ipGeoDef->put_GeometryType(esriGeometryPoint); ipGeoDef->put_HasM(VARIANT_FALSE); ipGeoDef->put_HasZ(VARIANT_FALSE); ipGeoDef->putref_SpatialReference(pSpatialRef); ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SHAPE)); ipFieldEdit->put_IsNullable(VARIANT_TRUE); ipFieldEdit->put_Type(esriFieldTypeGeometry); ipFieldEdit->putref_GeometryDef(ipGeoDef); } ipFieldsEdit->AddField(ipFieldEdit); // Create and add a Name field ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_NAME)); ipFieldEdit->put_Type(esriFieldTypeString); ipFieldEdit->put_Length(128); ipFieldsEdit->AddField(ipFieldEdit); // Add the NALocation fields AddLocationFields(ipFieldsEdit, pDENDS); // Add the new fields to the class definition (these must be set before setting field types) ipClassDefEdit->putref_Fields(ipFields); // Setup the field types (i.e., input/output fields, editable/non-editable fields, visible/non-visible fields, or a combination of these) AddLocationFieldTypes(ipFields, ipClassDefEdit); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_OID), esriNAFieldTypeInput | esriNAFieldTypeNotEditable); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_SHAPE), esriNAFieldTypeInput | esriNAFieldTypeNotEditable | esriNAFieldTypeNotVisible); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_NAME), esriNAFieldTypeInput); // Setup whether the NAClass is considered as input/output ipClassDefEdit->put_IsInput(VARIANT_TRUE); ipClassDefEdit->put_IsOutput(VARIANT_FALSE); // Setup necessary cardinality for allowing a Solve to be run // NOTE: the LowerBound property is used to define the minimum number of required NALocationObjects that are required by the solver // to perform analysis. The UpperBound property is used to define the maximum number of NALocationObjects that are allowed by the solver // to perform analysis. // In our case, we must have at least one seed point stored in the class before allowing Solve to enabled in ArcMap, and we have no UpperBound ipClassDefEdit->put_LowerBound(1); // Give the class definition a name... ipClassDefEdit->put_Name(CComBSTR(CS_SEED_POINTS_NAME)); // ...and add it to the named set ipClassDefinitions->Add(CComBSTR(CS_SEED_POINTS_NAME), (IUnknownPtr)ipClassDef); ////////////////////////////////////////////////////////// // Barriers class definition ipClassDef.CreateInstance(CLSID_NAClassDefinition); ipClassDefEdit = ipClassDef; ipIUID.CreateInstance(CLSID_UID); if (FAILED(hr = ipIUID->put_Value(CComVariant(L"esriNetworkAnalyst.NALocationFeature")))) return hr; ipClassDefEdit->putref_ClassCLSID(ipIUID); ipFields.CreateInstance(CLSID_Fields); ipFieldsEdit = ipFields; ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_OID)); ipFieldEdit->put_Type(esriFieldTypeOID); ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; { IGeometryDefEditPtr ipGeoDef(CLSID_GeometryDef); ipGeoDef->put_GeometryType(esriGeometryPoint); ipGeoDef->put_HasM(VARIANT_FALSE); ipGeoDef->put_HasZ(VARIANT_FALSE); ipGeoDef->putref_SpatialReference(pSpatialRef); ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SHAPE)); ipFieldEdit->put_IsNullable(VARIANT_TRUE); ipFieldEdit->put_Type(esriFieldTypeGeometry); ipFieldEdit->putref_GeometryDef(ipGeoDef); } ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_NAME)); ipFieldEdit->put_Type(esriFieldTypeString); ipFieldEdit->put_Length(128); ipFieldsEdit->AddField(ipFieldEdit); AddLocationFields(ipFieldsEdit, pDENDS); ipClassDefEdit->putref_Fields(ipFields); AddLocationFieldTypes(ipFields, ipClassDefEdit); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_OID), esriNAFieldTypeInput | esriNAFieldTypeNotEditable); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_SHAPE), esriNAFieldTypeInput | esriNAFieldTypeNotEditable | esriNAFieldTypeNotVisible); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_NAME), esriNAFieldTypeInput); ipClassDefEdit->put_IsInput(VARIANT_TRUE); ipClassDefEdit->put_IsOutput(VARIANT_FALSE); ipClassDefEdit->put_Name(CComBSTR(CS_BARRIERS_NAME)); ipClassDefinitions->Add(CComBSTR(CS_BARRIERS_NAME), (IUnknownPtr)ipClassDef); ////////////////////////////////////////////////////////// // Lines class definition ipClassDef.CreateInstance(CLSID_NAClassDefinition); ipClassDefEdit = ipClassDef; ipIUID.CreateInstance(CLSID_UID); if (FAILED(hr = ipIUID->put_Value(CComVariant(L"esriGeoDatabase.Feature")))) return hr; ipClassDefEdit->putref_ClassCLSID(ipIUID); ipFields.CreateInstance(CLSID_Fields); ipFieldsEdit = ipFields; ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_OID)); ipFieldEdit->put_Type(esriFieldTypeOID); ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; { IGeometryDefEditPtr ipGeoDef(CLSID_GeometryDef); ipGeoDef->put_GeometryType(esriGeometryPolyline); ipGeoDef->put_HasM(VARIANT_FALSE); ipGeoDef->put_HasZ(VARIANT_FALSE); ipGeoDef->putref_SpatialReference(pSpatialRef); ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SHAPE)); ipFieldEdit->put_IsNullable(VARIANT_TRUE); ipFieldEdit->put_Type(esriFieldTypeGeometry); ipFieldEdit->putref_GeometryDef(ipGeoDef); } ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SOURCE_ID)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SOURCE_OID)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_FROM_POS)); ipFieldEdit->put_Type(esriFieldTypeDouble); ipFieldsEdit->AddField(ipFieldEdit); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_TO_POS)); ipFieldEdit->put_Type(esriFieldTypeDouble); ipFieldsEdit->AddField(ipFieldEdit); ipClassDefEdit->putref_Fields(ipFields); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_OID), esriNAFieldTypeOutput | esriNAFieldTypeNotEditable); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_SHAPE), esriNAFieldTypeOutput | esriNAFieldTypeNotEditable | esriNAFieldTypeNotVisible); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_SOURCE_ID), esriNAFieldTypeOutput); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_SOURCE_OID), esriNAFieldTypeOutput); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_FROM_POS), esriNAFieldTypeOutput); ipClassDefEdit->put_FieldType(CComBSTR(CS_FIELD_TO_POS), esriNAFieldTypeOutput); ipClassDefEdit->put_IsInput(VARIANT_FALSE); ipClassDefEdit->put_IsOutput(VARIANT_TRUE); ipClassDefEdit->put_Name(CComBSTR(CS_LINES_NAME)); ipClassDefinitions->Add(CComBSTR(CS_LINES_NAME), (IUnknownPtr)ipClassDef); // Return the class definitions once we have finished ipClassDefinitions->AddRef(); *ppDefinitions = ipClassDefinitions; return S_OK; } HRESULT ConnectivitySolver::GetNAClassTable(INAContext* pContext, BSTR className, ITable** ppTable) { if (!pContext || !ppTable) return E_POINTER; HRESULT hr; INamedSetPtr ipNamedSet; if (FAILED(hr = pContext->get_NAClasses(&ipNamedSet))) return hr; IUnknownPtr ipUnk; if (FAILED(hr = ipNamedSet->get_ItemByName(className, &ipUnk))) return hr; ITablePtr ipTable(ipUnk); if (!ipTable) return AtlReportError(GetObjectCLSID(), _T("Context has an invalid NAClass."), IID_INASolver); ipTable->AddRef(); *ppTable = ipTable; return S_OK; } HRESULT ConnectivitySolver::LoadBarriers(ITable* pTable, INetworkQuery* pNetworkQuery, INetworkForwardStarEx* pNetworkForwardStarEx) { if (!pTable || !pNetworkForwardStarEx) return E_POINTER; HRESULT hr; // Initialize all network elements as traversable if (FAILED(hr = pNetworkForwardStarEx->RemoveElementRestrictions())) return hr; // Get a cursor on the table to loop through each row ICursorPtr ipCursor; if (FAILED(hr = pTable->Search(0, VARIANT_TRUE, &ipCursor))) return hr; // Create variables for looping through the cursor and setting up barriers IRowPtr ipRow; INALocationObjectPtr ipNALocationObject; INALocationPtr ipNALocation(CLSID_NALocation); IEnumNetworkElementPtr ipEnumNetworkElement; long sourceID = -1, sourceOID = -1; double posAlong, fromPosition, toPosition; INetworkEdgePtr ipReverseEdge; // we need one edge element for querying the opposing direction INetworkElementPtr ipElement; pNetworkQuery->CreateNetworkElement(esriNETEdge, &ipElement); ipReverseEdge = ipElement; esriNetworkElementType elementType; // Loop through the cursor getting the NALocation of each NALocationObject, // then query the network elements associated with each NALocation, // and set their traversability to false while (ipCursor->NextRow(&ipRow) == S_OK) { ipNALocationObject = ipRow; if (!ipNALocationObject) // we only want valid NALocationObjects continue; if (FAILED(hr = ipNALocationObject->QueryNALocation(ipNALocation))) return hr; // Once we have the NALocation, we need to check if it is actually located within the network dataset VARIANT_BOOL isLocated = VARIANT_FALSE; if (ipNALocation) { if (FAILED(hr = ipNALocation->get_IsLocated(&isLocated))) return hr; } // We are only concerned with located NALocations if (isLocated) { // Get the SourceID for the NALocation if (FAILED(hr = ipNALocation->get_SourceID(&sourceID))) return hr; // Get the SourceOID for the NALocation if (FAILED(hr = ipNALocation->get_SourceOID(&sourceOID))) return hr; // Get the PosAlong for the NALocation if (FAILED(hr = ipNALocation->get_SourcePosition(&posAlong))) return hr; // Once we have a located NALocation, we query the network to obtain its associated network elements if (FAILED(hr = pNetworkQuery->get_ElementsByOID(sourceID, sourceOID, &ipEnumNetworkElement))) return hr; // Set the traversability for all appropriate network elements to false ipEnumNetworkElement->Reset(); while (ipEnumNetworkElement->Next(&ipElement) == S_OK) { if (FAILED(hr = ipElement->get_ElementType(&elementType))) return hr; switch (elementType) { case esriNETJunction: { INetworkJunctionPtr ipJunction(ipElement); // If it is a junction, we can just set the element to non-traversable if (FAILED(hr = pNetworkForwardStarEx->AddJunctionRestriction(ipJunction))) return hr; break; } case esriNETEdge: { // If it is an edge, we must make sure it is an appropriate edge to set as non-traversable (by checking its fromPosition and toPosition) INetworkEdgePtr ipEdge(ipElement); if (FAILED(hr = ipEdge->QueryPositions(&fromPosition, &toPosition))) return hr; if (fromPosition <= posAlong && posAlong <= toPosition) { // Our NALocation lies along this edge element // Set it as non-traversable in both directions // First, set this edge element as non-traversable in the AlongDigitized direction (this is the default direction of edge elements returned from the get_ElementsByOID method above) INetworkEdgePtr ipEdge(ipElement); if (FAILED(hr = pNetworkForwardStarEx->AddEdgeRestriction(ipEdge, 0.0, 1.0))) return hr; // Then, query the edge element to get its opposite direction if (FAILED(hr = ipEdge->QueryEdgeInOtherDirection(ipReverseEdge))) return hr; // Finally, set this edge as non-traversable in the AgainstDigitized direction if (FAILED(hr = pNetworkForwardStarEx->AddEdgeRestriction(ipReverseEdge, 0.0, 1.0))) return hr; break; } } } } } } return S_OK; } HRESULT ConnectivitySolver::AddLocationFields(IFieldsEdit* pFieldsEdit, IDENetworkDataset* pDENDS) { if (!pFieldsEdit) return E_POINTER; HRESULT hr; IFieldPtr ipField; IFieldEditPtr ipFieldEdit; IRangeDomainPtr ipRangeDomain; CComVariant longDefaultValue(static_cast<long>(-1)); CComVariant doubleDefaultValue(static_cast<double>(-1)); ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SOURCE_ID)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldEdit->put_DefaultValue(longDefaultValue); ipFieldEdit->put_Precision(0); ipFieldEdit->put_Required(VARIANT_TRUE); ipFieldEdit->put_Length(4); // if we can get source names, then add them to a coded value domain if (pDENDS) { IArrayPtr ipSourceArray; if (FAILED(hr = pDENDS->get_Sources(&ipSourceArray))) return hr; ICodedValueDomainPtr ipCodedValueDomain(CLSID_CodedValueDomain); IDomainPtr(ipCodedValueDomain)->put_Name(CS_FIELD_SOURCE_ID); IDomainPtr(ipCodedValueDomain)->put_FieldType(esriFieldTypeInteger); IUnknownPtr ipUnk; INetworkSourcePtr ipSource; long sourceCount; ipSourceArray->get_Count(&sourceCount); for (long i = 0; i < sourceCount; i++) { ipSourceArray->get_Element(i, &ipUnk); ipSource = ipUnk; long sourceID; ipSource->get_ID(&sourceID); CComBSTR name; ipSource->get_Name(&name); CComVariant value(sourceID); ipCodedValueDomain->AddCode(value, name); } ipFieldEdit->putref_Domain((IDomainPtr)ipCodedValueDomain); } pFieldsEdit->AddField(ipFieldEdit); // Add the source OID field ipField.CreateInstance(CLSID_Field); ipRangeDomain.CreateInstance(CLSID_RangeDomain); IDomainPtr(ipRangeDomain)->put_Name(CS_FIELD_SOURCE_OID); IDomainPtr(ipRangeDomain)->put_FieldType(esriFieldTypeInteger); ipRangeDomain->put_MinValue(CComVariant((long)-1)); ipRangeDomain->put_MaxValue(CComVariant((long)LONG_MAX)); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SOURCE_OID)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldEdit->put_DefaultValue(longDefaultValue); ipFieldEdit->put_Precision(0); ipFieldEdit->put_Length(4); ipFieldEdit->put_Required(VARIANT_TRUE); ipFieldEdit->putref_Domain((IDomainPtr)ipRangeDomain); pFieldsEdit->AddField(ipFieldEdit); // Add the position field ipField.CreateInstance(CLSID_Field); ipRangeDomain.CreateInstance(CLSID_RangeDomain); IDomainPtr(ipRangeDomain)->put_Name(CS_FIELD_POSITION); IDomainPtr(ipRangeDomain)->put_FieldType(esriFieldTypeDouble); ipRangeDomain->put_MinValue(CComVariant((double)0.0)); ipRangeDomain->put_MaxValue(CComVariant((double)1.0)); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_POSITION)); ipFieldEdit->put_Type(esriFieldTypeDouble); ipFieldEdit->put_DefaultValue(doubleDefaultValue); ipFieldEdit->put_Precision(0); ipFieldEdit->put_Length(8); ipFieldEdit->put_Required(VARIANT_TRUE); ipFieldEdit->putref_Domain((IDomainPtr)ipRangeDomain); pFieldsEdit->AddField(ipFieldEdit); // Add the side of Edge domain ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_SIDE_OF_EDGE)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldEdit->put_DefaultValue(longDefaultValue); ipFieldEdit->put_Precision(0); ipFieldEdit->put_Length(4); ipFieldEdit->put_Required(VARIANT_TRUE); IDomainPtr ipSideDomain; CreateSideOfEdgeDomain(&ipSideDomain); ipFieldEdit->putref_Domain(ipSideDomain); pFieldsEdit->AddField(ipFieldEdit); // Add the curb approach domain ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_CURBAPPROACH)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldEdit->put_DefaultValue(CComVariant((long)esriNAEitherSideOfVehicle)); ipFieldEdit->put_Precision(0); ipFieldEdit->put_Length(4); ipFieldEdit->put_Required(VARIANT_TRUE); IDomainPtr ipCurbDomain; CreateCurbApproachDomain(&ipCurbDomain); ipFieldEdit->putref_Domain(ipCurbDomain); pFieldsEdit->AddField(ipFieldEdit); // Add the status field ipField.CreateInstance(CLSID_Field); ipFieldEdit = ipField; ipFieldEdit->put_Name(CComBSTR(CS_FIELD_STATUS)); ipFieldEdit->put_Type(esriFieldTypeInteger); ipFieldEdit->put_DefaultValue(CComVariant((long)0)); ipFieldEdit->put_Precision(0); ipFieldEdit->put_Length(4); ipFieldEdit->put_Required(VARIANT_TRUE); // set up coded value domains for the the status values ICodedValueDomainPtr ipCodedValueDomain(CLSID_CodedValueDomain); CreateStatusCodedValueDomain(ipCodedValueDomain); ipFieldEdit->putref_Domain((IDomainPtr)ipCodedValueDomain); pFieldsEdit->AddField(ipFieldEdit); return S_OK; } HRESULT ConnectivitySolver::CreateSideOfEdgeDomain(IDomain** ppDomain) { if (!ppDomain) return E_POINTER; HRESULT hr; ICodedValueDomainPtr ipCodedValueDomain(CLSID_CodedValueDomain); // Domains need names in order to serialize them // IDomainPtr(ipCodedValueDomain)->put_Name(CComBSTR(CS_SIDEOFEDGE_DOMAINNAME)); IDomainPtr(ipCodedValueDomain)->put_FieldType(esriFieldTypeInteger); CComVariant value(static_cast<long>(esriNAEdgeSideLeft)); if (FAILED(hr = ipCodedValueDomain->AddCode(value, CComBSTR(L"Left Side")))) return hr; value.lVal = esriNAEdgeSideRight; if (FAILED(hr = ipCodedValueDomain->AddCode(value, CComBSTR(L"Right Side")))) return hr; *ppDomain = (IDomainPtr)ipCodedValueDomain; ipCodedValueDomain->AddRef(); return S_OK; } HRESULT ConnectivitySolver::CreateCurbApproachDomain(IDomain** ppDomain) { if (!ppDomain) return E_POINTER; HRESULT hr; ICodedValueDomainPtr ipCodedValueDomain(CLSID_CodedValueDomain); IDomainPtr(ipCodedValueDomain)->put_Name(CComBSTR(CS_CURBAPPROACH_DOMAINNAME)); IDomainPtr(ipCodedValueDomain)->put_FieldType(esriFieldTypeInteger); CComVariant value(static_cast<long>(esriNAEitherSideOfVehicle)); if (FAILED(hr = ipCodedValueDomain->AddCode(value, CComBSTR(L"Either side of vehicle")))) return hr; value.lVal = esriNARightSideOfVehicle; if (FAILED(hr = ipCodedValueDomain->AddCode(value, CComBSTR(L"Right side of vehicle")))) return hr; value.lVal = esriNALeftSideOfVehicle; if (FAILED(hr = ipCodedValueDomain->AddCode(value, CComBSTR(L"Left side of vehicle")))) return hr; *ppDomain = (IDomainPtr)ipCodedValueDomain; ipCodedValueDomain->AddRef(); return S_OK; } HRESULT ConnectivitySolver::CreateStatusCodedValueDomain(ICodedValueDomain* pCodedValueDomain) { if (!pCodedValueDomain) return E_POINTER; IDomainPtr(pCodedValueDomain)->put_Name(CComBSTR(CS_LOCATIONSTATUS_DOMAINNAME)); IDomainPtr(pCodedValueDomain)->put_FieldType(esriFieldTypeInteger); CComVariant value(static_cast<long>(esriNAObjectStatusOK)); // this code is exclusive with the other codes pCodedValueDomain->AddCode(value, CComBSTR(L"OK")); value.lVal = esriNAObjectStatusNotLocated; pCodedValueDomain->AddCode(value, CComBSTR(L"Not located")); value.lVal = esriNAObjectStatusElementNotLocated; pCodedValueDomain->AddCode(value, CComBSTR(L"Network element not located")); value.lVal = esriNAObjectStatusElementNotTraversable; pCodedValueDomain->AddCode(value, CComBSTR(L"Element not traversible")); value.lVal = esriNAObjectStatusInvalidFieldValues; pCodedValueDomain->AddCode(value, CComBSTR(L"Invalid field values")); value.lVal = esriNAObjectStatusNotReached; pCodedValueDomain->AddCode(value, CComBSTR(L"Not reached")); value.lVal = esriNAObjectStatusTimeWindowViolation; pCodedValueDomain->AddCode(value, CComBSTR(L"Time window violation")); return S_OK; } HRESULT ConnectivitySolver::AddLocationFieldTypes(IFields* pFields, INAClassDefinitionEdit* pClassDef) { if (!pClassDef) return E_INVALIDARG; pClassDef->put_FieldType(CComBSTR(CS_FIELD_SOURCE_ID), esriNAFieldTypeInput); pClassDef->put_FieldType(CComBSTR(CS_FIELD_SOURCE_OID), esriNAFieldTypeInput); pClassDef->put_FieldType(CComBSTR(CS_FIELD_POSITION), esriNAFieldTypeInput); pClassDef->put_FieldType(CComBSTR(CS_FIELD_SIDE_OF_EDGE), esriNAFieldTypeInput); pClassDef->put_FieldType(CComBSTR(CS_FIELD_CURBAPPROACH), esriNAFieldTypeInput); pClassDef->put_FieldType(CComBSTR(CS_FIELD_STATUS), esriNAFieldTypeInput | esriNAFieldTypeNotEditable); return S_OK; } HRESULT ConnectivitySolver::OutputLineFeatures(long sourceID, long featureCount, segment_hash& segmentRecordHashTable, IFeatureClass* pSourceFC, IFeatureCursor* pInsertFeatureCursor, IFeatureBuffer* pFeatureBuffer) { if (!pSourceFC || !pInsertFeatureCursor || !pFeatureBuffer) return E_POINTER; if(sourceID < 0 || featureCount <= 0) return E_INVALIDARG; HRESULT hr; // Query for the appropriate field index values in the "LineData" feature class IFieldsPtr ipFields; if (FAILED(hr = pFeatureBuffer->get_Fields(&ipFields))) return hr; long sourceIDFieldIndex, sourceOIDFieldIndex, fromPositionFieldIndex, toPositionFieldIndex; if (FAILED(hr = ipFields->FindField(CComBSTR(CS_FIELD_SOURCE_ID), &sourceIDFieldIndex))) return hr; if (FAILED(hr = ipFields->FindField(CComBSTR(CS_FIELD_SOURCE_OID), &sourceOIDFieldIndex))) return hr; if (FAILED(hr = ipFields->FindField(CComBSTR(CS_FIELD_FROM_POS), &fromPositionFieldIndex))) return hr; if (FAILED(hr = ipFields->FindField(CComBSTR(CS_FIELD_TO_POS), &toPositionFieldIndex))) return hr; long* oidArray = new long[featureCount]; segment_hash::const_iterator segmentIterator; long i = 0; for (segmentIterator = segmentRecordHashTable.begin(); segmentIterator != segmentRecordHashTable.end(); segmentIterator = segmentRecordHashTable.upper_bound(segmentIterator->first)) oidArray[i++] = segmentIterator->first; // pass all unique OID keys into the oidArray // Create a VARIANT array of the OID values CComVariant oids; oids.vt = VT_I4 | VT_ARRAY; oids.parray = ::SafeArrayCreateVector(VT_I4, 0, featureCount); void* p; ::SafeArrayAccessData(oids.parray, &p); ::CopyMemory(p, (void*)oidArray, oids.parray->cbElements * featureCount); ::SafeArrayUnaccessData(oids.parray); // Get the features from the network source feature class that correspond to this list of OIDs IFeatureCursorPtr ipSourceFeatureCursor; if (FAILED(hr = pSourceFC->GetFeatures(oids, VARIANT_TRUE, &ipSourceFeatureCursor))) return hr; // Clean up dynamic memory allocation delete[] oidArray; // Loop through the source feature cursor, get the oid_bucket for each feature, and insert new features into the insert cursor IFeaturePtr ipSourceFeature; IGeometryPtr ipGeometry; oid_bucket edgeSegments; double fromPosition, toPosition; long sourceOID; CComVariant featureID(0); while (ipSourceFeatureCursor->NextFeature(&ipSourceFeature) == S_OK) { // Get the OID for this source if(FAILED(hr = ipSourceFeature->get_OID(&sourceOID))) return hr; // Get an iterator over all segmentRecords associated with this OID key edgeSegments = segmentRecordHashTable.equal_range(sourceOID); segmentIterator = edgeSegments.first; while (segmentIterator != edgeSegments.second) { // Get the segment record associated with this particular edge element segmentRecord currentSegmentRecord = segmentIterator->second; fromPosition = currentSegmentRecord.fromPosition; toPosition = currentSegmentRecord.toPosition; if (FAILED(hr = ipSourceFeature->get_Shape(&ipGeometry))) return hr; // Check to see how much of the line geometry we can copy over if (fromPosition != 0.0 || toPosition != 1.0) { // We must use only a subcurve of the line geometry ICurve3Ptr ipCurve(ipGeometry); ICurvePtr ipSubCurve; if (FAILED(hr = ipCurve->GetSubcurve(fromPosition, toPosition, VARIANT_TRUE, &ipSubCurve))) return hr; ipGeometry = ipSubCurve; } // Store the feature values on the feature buffer if (FAILED(hr = pFeatureBuffer->putref_Shape(ipGeometry))) return hr; if (FAILED(hr = pFeatureBuffer->put_Value(sourceIDFieldIndex, CComVariant(sourceID)))) return hr; if (FAILED(hr = pFeatureBuffer->put_Value(sourceOIDFieldIndex, CComVariant(sourceOID)))) return hr; if (FAILED(hr = pFeatureBuffer->put_Value(fromPositionFieldIndex, CComVariant(fromPosition)))) return hr; if (FAILED(hr = pFeatureBuffer->put_Value(toPositionFieldIndex, CComVariant(toPosition)))) return hr; // Insert the feature buffer in the insert cursor if (FAILED(hr = pInsertFeatureCursor->InsertFeature(pFeatureBuffer, &featureID))) return hr; segmentIterator++; } } // Empty segmentRecordHashTable segmentRecordHashTable.clear(); return S_OK; }