You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
986 lines
0 B
986 lines
0 B
6 years ago
|
/******************************************************************************
|
||
|
* $Id: sbnsearch.c,v 1.2 2016-12-05 12:44:05 erouault Exp $
|
||
|
*
|
||
|
* Project: Shapelib
|
||
|
* Purpose: Implementation of search in ESRI SBN spatial index.
|
||
|
* Author: Even Rouault, even dot rouault at mines dash paris dot org
|
||
|
*
|
||
|
******************************************************************************
|
||
|
* Copyright (c) 2012-2014, Even Rouault <even dot rouault at mines-paris dot org>
|
||
|
*
|
||
|
* This software is available under the following "MIT Style" license,
|
||
|
* or at the option of the licensee under the LGPL (see COPYING). This
|
||
|
* option is discussed in more detail in shapelib.html.
|
||
|
*
|
||
|
* --
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
* copy of this software and associated documentation files (the "Software"),
|
||
|
* to deal in the Software without restriction, including without limitation
|
||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||
|
* Software is furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included
|
||
|
* in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
* DEALINGS IN THE SOFTWARE.
|
||
|
******************************************************************************/
|
||
|
|
||
|
#include "shapefil.h"
|
||
|
|
||
|
#include <math.h>
|
||
|
#include <assert.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
SHP_CVSID("$Id: sbnsearch.c,v 1.2 2016-12-05 12:44:05 erouault Exp $")
|
||
|
|
||
|
#ifndef TRUE
|
||
|
# define TRUE 1
|
||
|
# define FALSE 0
|
||
|
#endif
|
||
|
|
||
|
#ifndef USE_CPL
|
||
|
#if defined(_MSC_VER)
|
||
|
# if _MSC_VER < 1900
|
||
|
# define snprintf _snprintf
|
||
|
# endif
|
||
|
#elif defined(WIN32) || defined(_WIN32)
|
||
|
# ifndef snprintf
|
||
|
# define snprintf _snprintf
|
||
|
# endif
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#define READ_MSB_INT(ptr) \
|
||
|
(((ptr)[0] << 24) | ((ptr)[1] << 16) | ((ptr)[2] << 8) | (ptr)[3])
|
||
|
|
||
|
#define CACHED_DEPTH_LIMIT 8
|
||
|
|
||
|
typedef unsigned char uchar;
|
||
|
|
||
|
typedef int coord;
|
||
|
/*typedef uchar coord;*/
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
uchar *pabyShapeDesc; /* Cache of (nShapeCount * 8) bytes of the bins. May be NULL. */
|
||
|
int nBinStart; /* Index of first bin for this node. */
|
||
|
int nShapeCount; /* Number of shapes attached to this node. */
|
||
|
int nBinCount; /* Number of bins for this node. May be 0 if node is empty. */
|
||
|
int nBinOffset; /* Offset in file of the start of the first bin. May be 0 if node is empty. */
|
||
|
|
||
|
int bBBoxInit; /* TRUE if the following bounding box has been computed. */
|
||
|
coord bMinX; /* Bounding box of the shapes directly attached to this node. */
|
||
|
coord bMinY; /* This is *not* the theoretical footprint of the node. */
|
||
|
coord bMaxX;
|
||
|
coord bMaxY;
|
||
|
} SBNNodeDescriptor;
|
||
|
|
||
|
struct SBNSearchInfo
|
||
|
{
|
||
|
SAHooks sHooks;
|
||
|
SAFile fpSBN;
|
||
|
SBNNodeDescriptor *pasNodeDescriptor;
|
||
|
int nShapeCount; /* Total number of shapes */
|
||
|
int nMaxDepth; /* Tree depth */
|
||
|
double dfMinX; /* Bounding box of all shapes */
|
||
|
double dfMaxX;
|
||
|
double dfMinY;
|
||
|
double dfMaxY;
|
||
|
|
||
|
#ifdef DEBUG_IO
|
||
|
int nTotalBytesRead;
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
SBNSearchHandle hSBN;
|
||
|
|
||
|
coord bMinX; /* Search bounding box */
|
||
|
coord bMinY;
|
||
|
coord bMaxX;
|
||
|
coord bMaxY;
|
||
|
|
||
|
int nShapeCount;
|
||
|
int nShapeAlloc;
|
||
|
int *panShapeId; /* 0 based */
|
||
|
|
||
|
uchar abyBinShape[8 * 100];
|
||
|
|
||
|
#ifdef DEBUG_IO
|
||
|
int nBytesRead;
|
||
|
#endif
|
||
|
} SearchStruct;
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SwapWord() */
|
||
|
/* */
|
||
|
/* Swap a 2, 4 or 8 byte word. */
|
||
|
/************************************************************************/
|
||
|
|
||
|
static void SwapWord( int length, void * wordP )
|
||
|
|
||
|
{
|
||
|
int i;
|
||
|
uchar temp;
|
||
|
|
||
|
for( i=0; i < length/2; i++ )
|
||
|
{
|
||
|
temp = ((uchar *) wordP)[i];
|
||
|
((uchar *)wordP)[i] = ((uchar *) wordP)[length-i-1];
|
||
|
((uchar *) wordP)[length-i-1] = temp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SBNOpenDiskTree() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
SBNSearchHandle SBNOpenDiskTree( const char* pszSBNFilename,
|
||
|
SAHooks *psHooks )
|
||
|
{
|
||
|
int i;
|
||
|
SBNSearchHandle hSBN;
|
||
|
uchar abyHeader[108];
|
||
|
int nShapeCount;
|
||
|
int nMaxDepth;
|
||
|
int nMaxNodes;
|
||
|
int nNodeDescSize;
|
||
|
int nNodeDescCount;
|
||
|
uchar* pabyData = NULL;
|
||
|
SBNNodeDescriptor* pasNodeDescriptor = NULL;
|
||
|
uchar abyBinHeader[8];
|
||
|
int nCurNode;
|
||
|
int nNextNonEmptyNode;
|
||
|
int nExpectedBinId;
|
||
|
int bBigEndian;
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Establish the byte order on this machine. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
i = 1;
|
||
|
if( *((unsigned char *) &i) == 1 )
|
||
|
bBigEndian = FALSE;
|
||
|
else
|
||
|
bBigEndian = TRUE;
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Initialize the handle structure. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
hSBN = (SBNSearchHandle)
|
||
|
calloc(sizeof(struct SBNSearchInfo),1);
|
||
|
|
||
|
if (psHooks == NULL)
|
||
|
SASetupDefaultHooks( &(hSBN->sHooks) );
|
||
|
else
|
||
|
memcpy( &(hSBN->sHooks), psHooks, sizeof(SAHooks) );
|
||
|
|
||
|
hSBN->fpSBN = hSBN->sHooks.FOpen(pszSBNFilename, "rb");
|
||
|
if (hSBN->fpSBN == NULL)
|
||
|
{
|
||
|
free(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Check file header signature. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
if (hSBN->sHooks.FRead(abyHeader, 108, 1, hSBN->fpSBN) != 1 ||
|
||
|
abyHeader[0] != 0 ||
|
||
|
abyHeader[1] != 0 ||
|
||
|
abyHeader[2] != 0x27 ||
|
||
|
(abyHeader[3] != 0x0A && abyHeader[3] != 0x0D) ||
|
||
|
abyHeader[4] != 0xFF ||
|
||
|
abyHeader[5] != 0xFF ||
|
||
|
abyHeader[6] != 0xFE ||
|
||
|
abyHeader[7] != 0x70)
|
||
|
{
|
||
|
hSBN->sHooks.Error( ".sbn file is unreadable, or corrupt." );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Read shapes bounding box. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
|
||
|
memcpy(&hSBN->dfMinX, abyHeader + 32, 8);
|
||
|
memcpy(&hSBN->dfMinY, abyHeader + 40, 8);
|
||
|
memcpy(&hSBN->dfMaxX, abyHeader + 48, 8);
|
||
|
memcpy(&hSBN->dfMaxY, abyHeader + 56, 8);
|
||
|
|
||
|
if( !bBigEndian )
|
||
|
{
|
||
|
SwapWord(8, &hSBN->dfMinX);
|
||
|
SwapWord(8, &hSBN->dfMinY);
|
||
|
SwapWord(8, &hSBN->dfMaxX);
|
||
|
SwapWord(8, &hSBN->dfMaxY);
|
||
|
}
|
||
|
|
||
|
if( hSBN->dfMinX > hSBN->dfMaxX ||
|
||
|
hSBN->dfMinY > hSBN->dfMaxY )
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Invalid extent in .sbn file." );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Read and check number of shapes. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
nShapeCount = READ_MSB_INT(abyHeader + 28);
|
||
|
hSBN->nShapeCount = nShapeCount;
|
||
|
if (nShapeCount < 0 || nShapeCount > 256000000 )
|
||
|
{
|
||
|
char szErrorMsg[64];
|
||
|
snprintf(szErrorMsg, sizeof(szErrorMsg),
|
||
|
"Invalid shape count in .sbn : %d", nShapeCount );
|
||
|
hSBN->sHooks.Error( szErrorMsg );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Empty spatial index */
|
||
|
if( nShapeCount == 0 )
|
||
|
{
|
||
|
return hSBN;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Compute tree depth. */
|
||
|
/* It is computed such as in average there are not more than 8 */
|
||
|
/* shapes per node. With a minimum depth of 2, and a maximum of 24 */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
nMaxDepth = 2;
|
||
|
while( nMaxDepth < 24 && nShapeCount > ((1 << nMaxDepth) - 1) * 8 )
|
||
|
nMaxDepth ++;
|
||
|
hSBN->nMaxDepth = nMaxDepth;
|
||
|
nMaxNodes = (1 << nMaxDepth) - 1;
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Check that the first bin id is 1. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
|
||
|
if( READ_MSB_INT(abyHeader + 100) != 1 )
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Unexpected bin id" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Read and check number of node descriptors to be read. */
|
||
|
/* There are at most (2^nMaxDepth) - 1, but all are not necessary */
|
||
|
/* described. Non described nodes are empty. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
nNodeDescSize = READ_MSB_INT(abyHeader + 104);
|
||
|
nNodeDescSize *= 2; /* 16-bit words */
|
||
|
|
||
|
/* each bin descriptor is made of 2 ints */
|
||
|
nNodeDescCount = nNodeDescSize / 8;
|
||
|
|
||
|
if ((nNodeDescSize % 8) != 0 ||
|
||
|
nNodeDescCount < 0 || nNodeDescCount > nMaxNodes )
|
||
|
{
|
||
|
char szErrorMsg[64];
|
||
|
snprintf(szErrorMsg, sizeof(szErrorMsg),
|
||
|
"Invalid node descriptor size in .sbn : %d", nNodeDescSize );
|
||
|
hSBN->sHooks.Error( szErrorMsg );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* coverity[tainted_data] */
|
||
|
pabyData = (uchar*) malloc( nNodeDescSize );
|
||
|
pasNodeDescriptor = (SBNNodeDescriptor*)
|
||
|
calloc ( nMaxNodes, sizeof(SBNNodeDescriptor) );
|
||
|
if (pabyData == NULL || pasNodeDescriptor == NULL)
|
||
|
{
|
||
|
free(pabyData);
|
||
|
free(pasNodeDescriptor);
|
||
|
hSBN->sHooks.Error( "Out of memory error" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Read node descriptors. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
if (hSBN->sHooks.FRead(pabyData, nNodeDescSize, 1,
|
||
|
hSBN->fpSBN) != 1)
|
||
|
{
|
||
|
free(pabyData);
|
||
|
free(pasNodeDescriptor);
|
||
|
hSBN->sHooks.Error( "Cannot read node descriptors" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
hSBN->pasNodeDescriptor = pasNodeDescriptor;
|
||
|
|
||
|
for(i = 0; i < nNodeDescCount; i++)
|
||
|
{
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Each node descriptor contains the index of the first bin that */
|
||
|
/* described it, and the number of shapes in this first bin and */
|
||
|
/* the following bins (in the relevant case). */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
int nBinStart = READ_MSB_INT(pabyData + 8 * i);
|
||
|
int nNodeShapeCount = READ_MSB_INT(pabyData + 8 * i + 4);
|
||
|
pasNodeDescriptor[i].nBinStart = nBinStart > 0 ? nBinStart : 0;
|
||
|
pasNodeDescriptor[i].nShapeCount = nNodeShapeCount;
|
||
|
|
||
|
if ((nBinStart > 0 && nNodeShapeCount == 0) ||
|
||
|
nNodeShapeCount < 0 || nNodeShapeCount > nShapeCount)
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Inconsistent shape count in bin" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(pabyData);
|
||
|
pabyData = NULL;
|
||
|
|
||
|
/* Locate first non-empty node */
|
||
|
nCurNode = 0;
|
||
|
while(nCurNode < nMaxNodes && pasNodeDescriptor[nCurNode].nBinStart <= 0)
|
||
|
nCurNode ++;
|
||
|
|
||
|
if( nCurNode >= nMaxNodes)
|
||
|
{
|
||
|
hSBN->sHooks.Error( "All nodes are empty" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
pasNodeDescriptor[nCurNode].nBinOffset =
|
||
|
(int) hSBN->sHooks.FTell(hSBN->fpSBN);
|
||
|
|
||
|
/* Compute the index of the next non empty node. */
|
||
|
nNextNonEmptyNode = nCurNode + 1;
|
||
|
while(nNextNonEmptyNode < nMaxNodes &&
|
||
|
pasNodeDescriptor[nNextNonEmptyNode].nBinStart <= 0)
|
||
|
nNextNonEmptyNode ++;
|
||
|
|
||
|
nExpectedBinId = 1;
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Traverse bins to compute the offset of the first bin of each */
|
||
|
/* node. */
|
||
|
/* Note: we could use the .sbx file to compute the offsets instead.*/
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
while( hSBN->sHooks.FRead(abyBinHeader, 8, 1,
|
||
|
hSBN->fpSBN) == 1 )
|
||
|
{
|
||
|
int nBinId;
|
||
|
int nBinSize;
|
||
|
|
||
|
nExpectedBinId ++;
|
||
|
|
||
|
nBinId = READ_MSB_INT(abyBinHeader);
|
||
|
nBinSize = READ_MSB_INT(abyBinHeader + 4);
|
||
|
nBinSize *= 2; /* 16-bit words */
|
||
|
|
||
|
if( nBinId != nExpectedBinId )
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Unexpected bin id" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Bins are always limited to 100 features */
|
||
|
/* If there are more, then they are located in continuous bins */
|
||
|
if( (nBinSize % 8) != 0 || nBinSize <= 0 || nBinSize > 100 * 8)
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Unexpected bin size" );
|
||
|
SBNCloseDiskTree(hSBN);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if( nNextNonEmptyNode < nMaxNodes &&
|
||
|
nBinId == pasNodeDescriptor[nNextNonEmptyNode].nBinStart )
|
||
|
{
|
||
|
nCurNode = nNextNonEmptyNode;
|
||
|
pasNodeDescriptor[nCurNode].nBinOffset =
|
||
|
(int) hSBN->sHooks.FTell(hSBN->fpSBN) - 8;
|
||
|
|
||
|
/* Compute the index of the next non empty node. */
|
||
|
nNextNonEmptyNode = nCurNode + 1;
|
||
|
while(nNextNonEmptyNode < nMaxNodes &&
|
||
|
pasNodeDescriptor[nNextNonEmptyNode].nBinStart <= 0)
|
||
|
nNextNonEmptyNode ++;
|
||
|
}
|
||
|
|
||
|
pasNodeDescriptor[nCurNode].nBinCount ++;
|
||
|
|
||
|
/* Skip shape description */
|
||
|
hSBN->sHooks.FSeek(hSBN->fpSBN, nBinSize, SEEK_CUR);
|
||
|
}
|
||
|
|
||
|
return hSBN;
|
||
|
}
|
||
|
|
||
|
/***********************************************************************/
|
||
|
/* SBNCloseDiskTree() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
void SBNCloseDiskTree( SBNSearchHandle hSBN )
|
||
|
{
|
||
|
int i;
|
||
|
int nMaxNodes;
|
||
|
|
||
|
if (hSBN == NULL)
|
||
|
return;
|
||
|
|
||
|
if( hSBN->pasNodeDescriptor != NULL )
|
||
|
{
|
||
|
nMaxNodes = (1 << hSBN->nMaxDepth) - 1;
|
||
|
for(i = 0; i < nMaxNodes; i++)
|
||
|
{
|
||
|
if( hSBN->pasNodeDescriptor[i].pabyShapeDesc != NULL )
|
||
|
free(hSBN->pasNodeDescriptor[i].pabyShapeDesc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* printf("hSBN->nTotalBytesRead = %d\n", hSBN->nTotalBytesRead); */
|
||
|
|
||
|
hSBN->sHooks.FClose(hSBN->fpSBN);
|
||
|
free(hSBN->pasNodeDescriptor);
|
||
|
free(hSBN);
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SfRealloc() */
|
||
|
/* */
|
||
|
/* A realloc cover function that will access a NULL pointer as */
|
||
|
/* a valid input. */
|
||
|
/************************************************************************/
|
||
|
|
||
|
static void * SfRealloc( void * pMem, int nNewSize )
|
||
|
|
||
|
{
|
||
|
if( pMem == NULL )
|
||
|
return( (void *) malloc(nNewSize) );
|
||
|
else
|
||
|
return( (void *) realloc(pMem,nNewSize) );
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SBNAddShapeId() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
static int SBNAddShapeId( SearchStruct* psSearch,
|
||
|
int nShapeId )
|
||
|
{
|
||
|
if (psSearch->nShapeCount == psSearch->nShapeAlloc)
|
||
|
{
|
||
|
int* pNewPtr;
|
||
|
|
||
|
psSearch->nShapeAlloc =
|
||
|
(int) (((psSearch->nShapeCount + 100) * 5) / 4);
|
||
|
pNewPtr =
|
||
|
(int *) SfRealloc( psSearch->panShapeId,
|
||
|
psSearch->nShapeAlloc * sizeof(int) );
|
||
|
if( pNewPtr == NULL )
|
||
|
{
|
||
|
psSearch->hSBN->sHooks.Error( "Out of memory error" );
|
||
|
return FALSE;
|
||
|
}
|
||
|
psSearch->panShapeId = pNewPtr;
|
||
|
}
|
||
|
|
||
|
psSearch->panShapeId[psSearch->nShapeCount] = nShapeId;
|
||
|
psSearch->nShapeCount ++;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SBNSearchDiskInternal() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
/* Due to the way integer coordinates are rounded, */
|
||
|
/* we can use a strict intersection test, except when the node */
|
||
|
/* bounding box or the search bounding box is degenerated. */
|
||
|
#define SEARCH_BB_INTERSECTS(_bMinX, _bMinY, _bMaxX, _bMaxY) \
|
||
|
(((bSearchMinX < _bMaxX && bSearchMaxX > _bMinX) || \
|
||
|
((_bMinX == _bMaxX || bSearchMinX == bSearchMaxX) && \
|
||
|
bSearchMinX <= _bMaxX && bSearchMaxX >= _bMinX)) && \
|
||
|
((bSearchMinY < _bMaxY && bSearchMaxY > _bMinY) || \
|
||
|
((_bMinY == _bMaxY || bSearchMinY == bSearchMaxY ) && \
|
||
|
bSearchMinY <= _bMaxY && bSearchMaxY >= _bMinY)))
|
||
|
|
||
|
|
||
|
static int SBNSearchDiskInternal( SearchStruct* psSearch,
|
||
|
int nDepth,
|
||
|
int nNodeId,
|
||
|
coord bNodeMinX,
|
||
|
coord bNodeMinY,
|
||
|
coord bNodeMaxX,
|
||
|
coord bNodeMaxY )
|
||
|
{
|
||
|
SBNSearchHandle hSBN;
|
||
|
SBNNodeDescriptor* psNode;
|
||
|
coord bSearchMinX = psSearch->bMinX;
|
||
|
coord bSearchMinY = psSearch->bMinY;
|
||
|
coord bSearchMaxX = psSearch->bMaxX;
|
||
|
coord bSearchMaxY = psSearch->bMaxY;
|
||
|
|
||
|
hSBN = psSearch->hSBN;
|
||
|
|
||
|
psNode = &(hSBN->pasNodeDescriptor[nNodeId]);
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Check if this node contains shapes that intersect the search */
|
||
|
/* bounding box. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
if ( psNode->bBBoxInit &&
|
||
|
!(SEARCH_BB_INTERSECTS(psNode->bMinX, psNode->bMinY,
|
||
|
psNode->bMaxX, psNode->bMaxY)) )
|
||
|
|
||
|
{
|
||
|
/* No intersection, then don't try to read the shapes attached */
|
||
|
/* to this node */
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* If this node contains shapes that are cached, then read them. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
else if (psNode->pabyShapeDesc != NULL)
|
||
|
{
|
||
|
int j;
|
||
|
uchar* pabyShapeDesc = psNode->pabyShapeDesc;
|
||
|
|
||
|
/* printf("nNodeId = %d, nDepth = %d\n", nNodeId, nDepth); */
|
||
|
|
||
|
for(j = 0; j < psNode->nShapeCount; j++)
|
||
|
{
|
||
|
coord bMinX = pabyShapeDesc[0];
|
||
|
coord bMinY = pabyShapeDesc[1];
|
||
|
coord bMaxX = pabyShapeDesc[2];
|
||
|
coord bMaxY = pabyShapeDesc[3];
|
||
|
|
||
|
if( SEARCH_BB_INTERSECTS(bMinX, bMinY, bMaxX, bMaxY) )
|
||
|
{
|
||
|
int nShapeId;
|
||
|
|
||
|
nShapeId = READ_MSB_INT(pabyShapeDesc + 4);
|
||
|
|
||
|
/* Caution : we count shape id starting from 0, and not 1 */
|
||
|
nShapeId --;
|
||
|
|
||
|
/*printf("shape=%d, minx=%d, miny=%d, maxx=%d, maxy=%d\n",
|
||
|
nShapeId, bMinX, bMinY, bMaxX, bMaxY);*/
|
||
|
|
||
|
if( !SBNAddShapeId( psSearch, nShapeId ) )
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
pabyShapeDesc += 8;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* If the node has attached shapes (that are not (yet) cached), */
|
||
|
/* then retrieve them from disk. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
|
||
|
else if (psNode->nBinCount > 0)
|
||
|
{
|
||
|
uchar abyBinHeader[8];
|
||
|
int nBinSize, nShapes;
|
||
|
int nShapeCountAcc = 0;
|
||
|
int i, j;
|
||
|
|
||
|
/* printf("nNodeId = %d, nDepth = %d\n", nNodeId, nDepth); */
|
||
|
|
||
|
hSBN->sHooks.FSeek(hSBN->fpSBN, psNode->nBinOffset, SEEK_SET);
|
||
|
|
||
|
if (nDepth < CACHED_DEPTH_LIMIT)
|
||
|
psNode->pabyShapeDesc = (uchar*) malloc(psNode->nShapeCount * 8);
|
||
|
|
||
|
for(i = 0; i < psNode->nBinCount; i++)
|
||
|
{
|
||
|
uchar* pabyBinShape;
|
||
|
|
||
|
#ifdef DEBUG_IO
|
||
|
psSearch->nBytesRead += 8;
|
||
|
#endif
|
||
|
if( hSBN->sHooks.FRead(abyBinHeader, 8, 1,
|
||
|
hSBN->fpSBN) != 1)
|
||
|
{
|
||
|
hSBN->sHooks.Error( "I/O error" );
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if ( READ_MSB_INT(abyBinHeader + 0) != psNode->nBinStart + i )
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Unexpected bin id" );
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
nBinSize = READ_MSB_INT(abyBinHeader + 4);
|
||
|
nBinSize *= 2; /* 16-bit words */
|
||
|
|
||
|
nShapes = nBinSize / 8;
|
||
|
|
||
|
/* Bins are always limited to 100 features */
|
||
|
if( (nBinSize % 8) != 0 || nShapes <= 0 || nShapes > 100)
|
||
|
{
|
||
|
hSBN->sHooks.Error( "Unexpected bin size" );
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if( nShapeCountAcc + nShapes > psNode->nShapeCount)
|
||
|
{
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
hSBN->sHooks.Error( "Inconsistent shape count for bin" );
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (nDepth < CACHED_DEPTH_LIMIT && psNode->pabyShapeDesc != NULL)
|
||
|
{
|
||
|
pabyBinShape = psNode->pabyShapeDesc + nShapeCountAcc * 8;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pabyBinShape = psSearch->abyBinShape;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_IO
|
||
|
psSearch->nBytesRead += nBinSize;
|
||
|
#endif
|
||
|
if (hSBN->sHooks.FRead(pabyBinShape, nBinSize, 1,
|
||
|
hSBN->fpSBN) != 1)
|
||
|
{
|
||
|
hSBN->sHooks.Error( "I/O error" );
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
nShapeCountAcc += nShapes;
|
||
|
|
||
|
if (i == 0 && !psNode->bBBoxInit)
|
||
|
{
|
||
|
psNode->bMinX = pabyBinShape[0];
|
||
|
psNode->bMinY = pabyBinShape[1];
|
||
|
psNode->bMaxX = pabyBinShape[2];
|
||
|
psNode->bMaxY = pabyBinShape[3];
|
||
|
}
|
||
|
|
||
|
for(j = 0; j < nShapes; j++)
|
||
|
{
|
||
|
coord bMinX = pabyBinShape[0];
|
||
|
coord bMinY = pabyBinShape[1];
|
||
|
coord bMaxX = pabyBinShape[2];
|
||
|
coord bMaxY = pabyBinShape[3];
|
||
|
|
||
|
if( !psNode->bBBoxInit )
|
||
|
{
|
||
|
#ifdef sanity_checks
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Those tests only check that the shape bounding box in the bin */
|
||
|
/* are consistent (self-consistent and consistent with the node */
|
||
|
/* they are attached to). They are optional however (as far as */
|
||
|
/* the safety of runtime is concerned at least). */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
|
||
|
if( !(((bMinX < bMaxX ||
|
||
|
(bMinX == 0 && bMaxX == 0) ||
|
||
|
(bMinX == 255 && bMaxX == 255))) &&
|
||
|
((bMinY < bMaxY ||
|
||
|
(bMinY == 0 && bMaxY == 0) ||
|
||
|
(bMinY == 255 && bMaxY == 255)))) ||
|
||
|
bMaxX < bNodeMinX || bMaxY < bNodeMinY ||
|
||
|
bMinX > bNodeMaxX || bMinY > bNodeMaxY )
|
||
|
{
|
||
|
/*printf("shape %d %d %d %d\n", bMinX, bMinY, bMaxX, bMaxY);
|
||
|
printf("node %d %d %d %d\n", bNodeMinX, bNodeMinY, bNodeMaxX, bNodeMaxY);*/
|
||
|
hSBN->sHooks.Error(
|
||
|
"Invalid shape bounding box in bin" );
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
if (bMinX < psNode->bMinX) psNode->bMinX = bMinX;
|
||
|
if (bMinY < psNode->bMinY) psNode->bMinY = bMinY;
|
||
|
if (bMaxX > psNode->bMaxX) psNode->bMaxX = bMaxX;
|
||
|
if (bMaxY > psNode->bMaxY) psNode->bMaxY = bMaxY;
|
||
|
}
|
||
|
|
||
|
if( SEARCH_BB_INTERSECTS(bMinX, bMinY, bMaxX, bMaxY) )
|
||
|
{
|
||
|
int nShapeId;
|
||
|
|
||
|
nShapeId = READ_MSB_INT(pabyBinShape + 4);
|
||
|
|
||
|
/* Caution : we count shape id starting from 0, and not 1 */
|
||
|
nShapeId --;
|
||
|
|
||
|
/*printf("shape=%d, minx=%d, miny=%d, maxx=%d, maxy=%d\n",
|
||
|
nShapeId, bMinX, bMinY, bMaxX, bMaxY);*/
|
||
|
|
||
|
if( !SBNAddShapeId( psSearch, nShapeId ) )
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
pabyBinShape += 8;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( nShapeCountAcc != psNode->nShapeCount)
|
||
|
{
|
||
|
free(psNode->pabyShapeDesc);
|
||
|
psNode->pabyShapeDesc = NULL;
|
||
|
hSBN->sHooks.Error( "Inconsistent shape count for bin" );
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
psNode->bBBoxInit = TRUE;
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Look up in child nodes. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
if( nDepth + 1 < hSBN->nMaxDepth )
|
||
|
{
|
||
|
nNodeId = nNodeId * 2 + 1;
|
||
|
|
||
|
if( (nDepth % 2) == 0 ) /* x split */
|
||
|
{
|
||
|
coord bMid = (coord) (1 + ((int)bNodeMinX + bNodeMaxX) / 2);
|
||
|
if( bSearchMinX <= bMid - 1 &&
|
||
|
!SBNSearchDiskInternal( psSearch, nDepth + 1, nNodeId + 1,
|
||
|
bNodeMinX, bNodeMinY,
|
||
|
bMid - 1, bNodeMaxY ) )
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
if( bSearchMaxX >= bMid &&
|
||
|
!SBNSearchDiskInternal( psSearch, nDepth + 1, nNodeId,
|
||
|
bMid, bNodeMinY,
|
||
|
bNodeMaxX, bNodeMaxY ) )
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else /* y split */
|
||
|
{
|
||
|
coord bMid = (coord) (1 + ((int)bNodeMinY + bNodeMaxY) / 2);
|
||
|
if( bSearchMinY <= bMid - 1 &&
|
||
|
!SBNSearchDiskInternal( psSearch, nDepth + 1, nNodeId + 1,
|
||
|
bNodeMinX, bNodeMinY,
|
||
|
bNodeMaxX, bMid - 1 ) )
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
if( bSearchMaxY >= bMid &&
|
||
|
!SBNSearchDiskInternal( psSearch, nDepth + 1, nNodeId,
|
||
|
bNodeMinX, bMid,
|
||
|
bNodeMaxX, bNodeMaxY ) )
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* compare_ints() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
/* helper for qsort */
|
||
|
static int
|
||
|
compare_ints( const void * a, const void * b)
|
||
|
{
|
||
|
return (*(int*)a) - (*(int*)b);
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SBNSearchDiskTree() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
int* SBNSearchDiskTree( SBNSearchHandle hSBN,
|
||
|
double *padfBoundsMin, double *padfBoundsMax,
|
||
|
int *pnShapeCount )
|
||
|
{
|
||
|
double dfMinX, dfMinY, dfMaxX, dfMaxY;
|
||
|
double dfDiskXExtent, dfDiskYExtent;
|
||
|
int bMinX, bMinY, bMaxX, bMaxY;
|
||
|
|
||
|
*pnShapeCount = 0;
|
||
|
|
||
|
dfMinX = padfBoundsMin[0];
|
||
|
dfMinY = padfBoundsMin[1];
|
||
|
dfMaxX = padfBoundsMax[0];
|
||
|
dfMaxY = padfBoundsMax[1];
|
||
|
|
||
|
if( dfMinX > dfMaxX || dfMinY > dfMaxY )
|
||
|
return NULL;
|
||
|
|
||
|
if( dfMaxX < hSBN->dfMinX || dfMaxY < hSBN->dfMinY ||
|
||
|
dfMinX > hSBN->dfMaxX || dfMinY > hSBN->dfMaxY )
|
||
|
return NULL;
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Compute the search coordinates in [0,255]x[0,255] coord. space */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
dfDiskXExtent = hSBN->dfMaxX - hSBN->dfMinX;
|
||
|
dfDiskYExtent = hSBN->dfMaxY - hSBN->dfMinY;
|
||
|
|
||
|
if ( dfDiskXExtent == 0.0 )
|
||
|
{
|
||
|
bMinX = 0;
|
||
|
bMaxX = 255;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( dfMinX < hSBN->dfMinX )
|
||
|
bMinX = 0;
|
||
|
else
|
||
|
{
|
||
|
double dfMinX_255 = (dfMinX - hSBN->dfMinX)
|
||
|
/ dfDiskXExtent * 255.0;
|
||
|
bMinX = (int)floor(dfMinX_255 - 0.005);
|
||
|
if( bMinX < 0 ) bMinX = 0;
|
||
|
}
|
||
|
|
||
|
if( dfMaxX > hSBN->dfMaxX )
|
||
|
bMaxX = 255;
|
||
|
else
|
||
|
{
|
||
|
double dfMaxX_255 = (dfMaxX - hSBN->dfMinX)
|
||
|
/ dfDiskXExtent * 255.0;
|
||
|
bMaxX = (int)ceil(dfMaxX_255 + 0.005);
|
||
|
if( bMaxX > 255 ) bMaxX = 255;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( dfDiskYExtent == 0.0 )
|
||
|
{
|
||
|
bMinY = 0;
|
||
|
bMaxY = 255;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( dfMinY < hSBN->dfMinY )
|
||
|
bMinY = 0;
|
||
|
else
|
||
|
{
|
||
|
double dfMinY_255 = (dfMinY - hSBN->dfMinY)
|
||
|
/ dfDiskYExtent * 255.0;
|
||
|
bMinY = (int)floor(dfMinY_255 - 0.005);
|
||
|
if( bMinY < 0 ) bMinY = 0;
|
||
|
}
|
||
|
|
||
|
if( dfMaxY > hSBN->dfMaxY )
|
||
|
bMaxY = 255;
|
||
|
else
|
||
|
{
|
||
|
double dfMaxY_255 = (dfMaxY - hSBN->dfMinY)
|
||
|
/ dfDiskYExtent * 255.0;
|
||
|
bMaxY = (int)ceil(dfMaxY_255 + 0.005);
|
||
|
if( bMaxY > 255 ) bMaxY = 255;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Run the search. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
|
||
|
return SBNSearchDiskTreeInteger(hSBN,
|
||
|
bMinX, bMinY, bMaxX, bMaxY,
|
||
|
pnShapeCount);
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SBNSearchDiskTreeInteger() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
int* SBNSearchDiskTreeInteger( SBNSearchHandle hSBN,
|
||
|
int bMinX, int bMinY, int bMaxX, int bMaxY,
|
||
|
int *pnShapeCount )
|
||
|
{
|
||
|
SearchStruct sSearch;
|
||
|
int bRet;
|
||
|
|
||
|
*pnShapeCount = 0;
|
||
|
|
||
|
if( bMinX > bMaxX || bMinY > bMaxY )
|
||
|
return NULL;
|
||
|
|
||
|
if( bMaxX < 0 || bMaxY < 0 || bMinX > 255 || bMinY > 255 )
|
||
|
return NULL;
|
||
|
|
||
|
if( hSBN->nShapeCount == 0 )
|
||
|
return NULL;
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Run the search. */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
memset( &sSearch, 0, sizeof(sSearch) );
|
||
|
sSearch.hSBN = hSBN;
|
||
|
sSearch.bMinX = (coord) (bMinX >= 0 ? bMinX : 0);
|
||
|
sSearch.bMinY = (coord) (bMinY >= 0 ? bMinY : 0);
|
||
|
sSearch.bMaxX = (coord) (bMaxX <= 255 ? bMaxX : 255);
|
||
|
sSearch.bMaxY = (coord) (bMaxY <= 255 ? bMaxY : 255);
|
||
|
sSearch.nShapeCount = 0;
|
||
|
sSearch.nShapeAlloc = 0;
|
||
|
sSearch.panShapeId = (int*) calloc(1, sizeof(int));
|
||
|
#ifdef DEBUG_IO
|
||
|
sSearch.nBytesRead = 0;
|
||
|
#endif
|
||
|
|
||
|
bRet = SBNSearchDiskInternal(&sSearch, 0, 0, 0, 0, 255, 255);
|
||
|
|
||
|
#ifdef DEBUG_IO
|
||
|
hSBN->nTotalBytesRead += sSearch.nBytesRead;
|
||
|
/* printf("nBytesRead = %d\n", sSearch.nBytesRead); */
|
||
|
#endif
|
||
|
|
||
|
if( !bRet )
|
||
|
{
|
||
|
free( sSearch.panShapeId );
|
||
|
*pnShapeCount = 0;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
*pnShapeCount = sSearch.nShapeCount;
|
||
|
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
/* Sort the id array */
|
||
|
/* -------------------------------------------------------------------- */
|
||
|
qsort(sSearch.panShapeId, *pnShapeCount, sizeof(int), compare_ints);
|
||
|
|
||
|
return sSearch.panShapeId;
|
||
|
}
|
||
|
|
||
|
/************************************************************************/
|
||
|
/* SBNSearchFreeIds() */
|
||
|
/************************************************************************/
|
||
|
|
||
|
void SBNSearchFreeIds( int* panShapeId )
|
||
|
{
|
||
|
free( panShapeId );
|
||
|
}
|