Changeset 934

Show
Ignore:
Timestamp:
03/09/12 00:11:07 (15 months ago)
Author:
jjr8
Message:

Completed ArcGISPoints.CreateFeatureClassWithPoints?.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • MGET/Branches/Jason/PythonPackage/src/GeoEco/SpatialAnalysis/Points.py

    r933 r934  
    1818# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
    1919 
     20import datetime 
     21import math 
    2022import os.path 
    2123 
     
    339341 
    340342    @classmethod 
    341     def CreatePointsAlongLines(cls, inputLines, outputFeatureClass, interval, unit, trackIDField=None, orderByField=None, startTimeField=None, endTimeField=None, pointsAtLineEnds=False, distributeRemainder=False, where=None, fieldsToCopy=None, overwriteExisting=False): 
     343    def CreatePointsAlongLines(cls, inputLines, outputFeatureClass, interval, unit, trackIDField=None, orderByField=None, startTimeField=None, endTimeField=None, pointsAtLineEnds=False, distributeRemainder=True, where=None, fieldsToCopy=None, overwriteExisting=False): 
    342344        cls.__doc__.Obj.ValidateMethodInvocation() 
    343345        try: 
    344             pass 
     346 
     347            # Perform additional validation. 
     348 
     349            isTemporal = unit in [u'days', u'hours', u'minutes', u'seconds'] 
     350            if isTemporal: 
     351                if startTimeField is None: 
     352                    raise ValueError(_(u'A value must be provided for the Start Time Field parameter when the Interval Unit is %(unit)s.') % {u'unit': unit}) 
     353                if endTimeField is None: 
     354                    raise ValueError(_(u'A value must be provided for the End Time Field parameter when the Interval Unit is %(unit)s.') % {u'unit': unit}) 
     355 
     356                if trackIDField is not None and trackIDField.lower() == u'pointtime': 
     357                    raise ValueError(_(u'If a temporal Interval Unit is specified (i.e. days, hours, minutes, or seconds) the Track ID Field may not be named PointTime.')) 
     358                if orderByField is not None and orderByField.lower() == u'pointtime': 
     359                    raise ValueError(_(u'If a temporal Interval Unit is specified (i.e. days, hours, minutes, or seconds) the Order By Field may not be named PointTime.')) 
     360                if startTimeField.lower() == u'pointtime': 
     361                    raise ValueError(_(u'If a temporal Interval Unit is specified (i.e. days, hours, minutes, or seconds) the Start Time Field may not be named PointTime.')) 
     362                if endTimeField.lower() == u'pointtime': 
     363                    raise ValueError(_(u'If a temporal Interval Unit is specified (i.e. days, hours, minutes, or seconds) the End Time Field may not be named PointTime.')) 
     364                if fieldsToCopy is not None and u'pointtime' in [f.lower() for f in fieldsToCopy]: 
     365                    raise ValueError(_(u'If a temporal Interval Unit is specified (i.e. days, hours, minutes, or seconds) the Fields To Copy parameter may not contain a field named PointTime.')) 
     366 
     367            from GeoEco.Datasets.ArcGIS import ArcGISWorkspace, ArcGISTable 
     368             
     369            linesTable = ArcGISTable(inputLines) 
     370            linesSR = linesTable.GetSpatialReference('obj') 
     371 
     372            if linesSR.IsProjected() and unit == u'decimal degrees': 
     373                raise ValueError(_(u'The input lines are in a projected coordinate system but the Interval Unit is decimal degrees. This is not allowed. When the lines are projected, the Interval Unit must be a linear or temporal unit (e.g. meters or days).')) 
     374 
     375            # If this the interval unit is temporal, convert the 
     376            # interval to a datetime.timedelta instance. 
     377 
     378            if isTemporal: 
     379                if unit == u'days': 
     380                    interval = datetime.timedelta(interval) 
     381                elif unit == u'hours': 
     382                    interval = datetime.timedelta(seconds=interval * 3600) 
     383                elif unit == u'minutes': 
     384                    interval = datetime.timedelta(seconds=interval * 60) 
     385                else: 
     386                    interval = datetime.timedelta(seconds=interval) 
     387 
     388            # Otherwise, if it is linear, convert it to meters. 
     389 
     390            elif unit != u'decimal degrees': 
     391                if unit == u'feet': 
     392                    interval *= 0.3048 
     393                elif unit == u'kilometers': 
     394                    interval *= 1000 
     395                elif unit == u'meters': 
     396                    pass 
     397                elif unit == u'miles': 
     398                    interval *= 1609.344 
     399                elif unit == u'nautical miles': 
     400                    interval *= 1852 
     401                elif unit == u'yards': 
     402                    interval *= 0.9144 
     403                else: 
     404                    raise ValueError(_(u'"%(unit)s" is an unknown linear unit. Please use feet, kilometers, meters, miles, nautical miles, or yards.') % {u'unit': unit}) 
     405 
     406            # Create the output feature class. 
     407 
     408            from GeoEco.ArcGIS import GeoprocessorManager 
     409            from GeoEco.Datasets import QueryableAttribute 
     410 
     411            workspace = ArcGISWorkspace(os.path.dirname(outputFeatureClass), ArcGISTable, pathParsingExpressions=[u'(?P<TableName>.+)'], queryableAttributes=(QueryableAttribute(u'TableName', _(u'Table name'), UnicodeStringTypeMetadata()),)) 
     412            outputTable = workspace.CreateTable(os.path.basename(outputFeatureClass), u'Point', linesSR) 
     413 
     414            sourceFields = [trackIDField, orderByField] 
     415            if fieldsToCopy is not None: 
     416                sourceFields.extend(fieldsToCopy) 
     417 
     418            gp = GeoprocessorManager.GetWrappedGeoprocessor() 
     419            destFields = [] 
     420            i = 0 
     421            while i < len(sourceFields): 
     422                if sourceFields[i] is None: 
     423                    del sourceFields[i] 
     424                    continue 
     425 
     426                field = linesTable.GetFieldByName(sourceFields[i]) 
     427                dataType = field.DataType 
     428                if dataType == u'oid': 
     429                    dataType = u'int32' 
     430                    destFields.append(sourceFields[i] + '_') 
     431                else: 
     432                    destFields.append(gp.ValidateFieldName(sourceFields[i], outputFeatureClass)) 
     433                     
     434                outputTable.AddField(destFields[i], dataType, field.Length, field.Precision, field.IsNullable) 
     435 
     436                i += 1 
     437 
     438            if isTemporal: 
     439                outputTable.AddField(u'PointTime', u'datetime') 
     440 
     441            # If the caller requested that the remaining distance on a 
     442            # track should be distributed evenly among all points, 
     443            # query the lines and tally the length of each track. 
     444 
     445            orderLinesBy = [] 
     446            if trackIDField is not None: 
     447                orderLinesBy.append(trackIDField + ' ASC') 
     448            if orderByField is not None: 
     449                orderLinesBy.append(orderByField + ' ASC') 
     450            else: 
     451                orderLinesBy.append(inputLines.OIDFieldName + ' ASC') 
     452            orderLinesBy = ', '.join(orderLinesBy) 
     453 
     454            if distributeRemainder: 
     455                Logger.Info(_(u'Querying %(lines)s and tallying the length of each track.') % {u'lines': inputLines}) 
     456 
     457                trackLengths = {} 
     458                gotLine = False 
     459                trackID = None 
     460                selectCursor = linesTable.OpenSelectCursor(where=where, orderBy=orderLinesBy) 
     461                try: 
     462                    while selectCursor.NextRow(): 
     463                        if trackIDField is not None: 
     464                            newTrackID = selectCursor.GetValue(trackIDField) 
     465                            if newTrackID != trackID and gotLine: 
     466                                trackLengths[trackID] = trackLength 
     467 
     468                        if not gotLine or trackIDField is not None and newTrackID != trackID: 
     469                            gotLine = True 
     470                             
     471                            if trackIDField is not None: 
     472                                trackID = newTrackID 
     473                            trackIsValid = True 
     474 
     475                            if isTemporal: 
     476                                trackLength = datetime.timedelta(0) 
     477                            else: 
     478                                trackLength = 0. 
     479 
     480                        if not trackIsValid: 
     481                            continue 
     482 
     483                        if isTemporal: 
     484                            duration = selectCursor.GetValue(endTimeField) - selectCursor.GetValue(startTimeField) 
     485                            if duration < datetime.timedelta(0): 
     486                                trackIsValid = False            # The duration of a line is not allowed to be less than zero. We will report a warning about this later. 
     487                                continue 
     488                            trackLength += duration 
     489                        else: 
     490                            trackLength += cls._GetLineLength(linesSR, unit, selectCursor, isTemporal) 
     491                finally: 
     492                    del selectCursor 
     493 
     494                if gotLine: 
     495                    trackLengths[trackID] = trackLength 
     496 
     497                # Now, for each track, calculate the interval that 
     498                # accounts for the remainder. 
     499 
     500                perTrackInterval = {} 
     501 
     502                for trackID, length in trackLengths.items(): 
     503                    if isTemporal: 
     504                        intervalInSeconds = interval.microseconds * 0.000001 + interval.seconds + interval.days * 86400. 
     505                        lengthInSeconds = length.microseconds * 0.000001 + length.seconds + length.days * 86400. 
     506                        if lengthInSeconds % intervalInSeconds == 0: 
     507                            perTrackInterval[trackID] = datetime.timedelta(seconds = lengthInSeconds / intervalInSeconds) 
     508                        else: 
     509                            perTrackInterval[trackID] = datetime.timedelta(seconds = lengthInSeconds / (lengthInSeconds // intervalInSeconds + 1)) 
     510 
     511                    elif length % interval == 0: 
     512                        perTrackInterval[trackID] = length / interval 
     513                    else: 
     514                        perTrackInterval[trackID] = length / (length // interval + 1) 
     515 
     516                    Logger.Debug(_(u'Track %r: length = %r, interval = %r') % (trackID, length, perTrackInterval[trackID])) 
     517 
     518            # Open an insert cursor on the output feature class and a 
     519            # select cursor on the lines. 
     520 
     521            Logger.Info(_(u'Inserting points into %(output)s.') % {u'output': outputFeatureClass}) 
     522 
     523            insertCursor = outputTable.OpenInsertCursor() 
     524            try: 
     525                gotLine = False 
     526                trackID = None 
     527                trackIsValid = False 
     528                selectCursor = linesTable.OpenSelectCursor(where=where, orderBy=orderLinesBy, reportProgress=False) 
     529                try: 
     530                    while selectCursor.NextRow(): 
     531 
     532                        # Read field values for this line. 
     533 
     534                        fieldValues = {} 
     535                        for i in range(len(sourceFields)): 
     536                            fieldValues[destFields[i]] = selectCursor.GetValue(sourceFields[i]) 
     537 
     538                        if isTemporal: 
     539                            startTime = selectCursor.GetValue(startTimeField) 
     540                            endTime = selectCursor.GetValue(endTimeField) 
     541                        else: 
     542                            startTime = None 
     543                            endTime = None 
     544 
     545                        # If there are multiple tracks, get the track 
     546                        # ID of this line. If this line is the start 
     547                        # of a new track, and the caller requested 
     548                        # that we place lines at the end of tracks, 
     549                        # and we have not done so already, insert the 
     550                        # end point. 
     551                         
     552                        if trackIDField is not None: 
     553                            newTrackID = selectCursor.GetValue(trackIDField) 
     554                            if newTrackID != trackID and pointsAtLineEnds and trackIsValid and (isTemporal and distanceSincePoint > datetime.timedelta(0) or not isTemporal and distanceSincePoint > 0) and endX is not None: 
     555                                Logger.Debug('Track %r: Writing end point' % trackID) 
     556                                cls._InsertPointForLine(endX, endY, endTime, insertCursor, destFields, fieldValues) 
     557 
     558                        # If this line is the start of a new track, 
     559                        # initialize our variables. 
     560 
     561                        if not gotLine or trackIDField is not None and newTrackID != trackID: 
     562                            gotLine = True 
     563                             
     564                            if trackIDField is not None: 
     565                                trackID = newTrackID 
     566                            trackIsValid = True 
     567                            startOfTrack = True 
     568                             
     569                            if isTemporal: 
     570                                distanceSincePoint = datetime.timedelta(0) 
     571                            else: 
     572                                distanceSincePoint = 0. 
     573 
     574                            Logger.Debug('Track %r: Started' % trackID) 
     575 
     576                            # Calculate the distance to the first 
     577                            # point (excluding the point that is on 
     578                            # the starting end of the line, if the 
     579                            # caller requested points on the ends). If 
     580                            # the caller caller requested that the 
     581                            # remaining distance on a track should be 
     582                            # distributed evenly among all points, the 
     583                            # the distance will be specific to this 
     584                            # track. If there are points at the line 
     585                            # ends, the distance will simply be the 
     586                            # track-specific point interval. If not, 
     587                            # then the distace will be one-half of 
     588                            # that interval. 
     589                            # 
     590                            # If the remaining distance should not be 
     591                            # distributed evenly, then the distance to 
     592                            # the first point is simply the point 
     593                            # interval requested by the caller. 
     594             
     595                            if distributeRemainder: 
     596                                if pointsAtLineEnds: 
     597                                    distanceToNextPoint = perTrackInterval[trackID] 
     598                                else: 
     599                                    distanceToNextPoint = perTrackInterval[trackID] / 2 
     600                            else: 
     601                                distanceToNextPoint = interval 
     602 
     603                        # If the remainder of the track is invalid 
     604                        # (because the caller specifeid a temporal 
     605                        # unit and a prior line on this track had a 
     606                        # negative duration), skip this line. 
     607 
     608                        if not trackIsValid: 
     609                            Logger.Debug('Track %r: Skipping line; remainder of track is not valid.' % trackID) 
     610                            continue 
     611 
     612                        # If the caller specified a temporal unit, 
     613                        # calculate the duration of this line. 
     614 
     615                        if isTemporal: 
     616                            duration = endTime - startTime 
     617                            Logger.Debug('Track %r: Duration of line = %r, distance to next point = %r.' % (trackID, duration, distanceToNextPoint)) 
     618 
     619                            # If the duration is negative, flag this 
     620                            # track as invalid and generate no further 
     621                            # points along it. 
     622                             
     623                            if duration.seconds < 0 and duration.days < 0: 
     624                                if trackIDField is None: 
     625                                    Logger.Warning(_(u'The line with %(oid)s of %(value)r has a %(f1)s that is greater than %(f2)s. This is not allowed; it would mean the line had a negative duration. No further points will be generated.') % {u'oid': linesTable.OIDFieldName, u'value': selectCursor.GetOID(), u'f1': startTimeField, u'f2': endTimeField}) 
     626                                else: 
     627                                    Logger.Warning(_(u'For the track with %(trackIDField)s of %(trackID)s, the line with %(oid)s of %(value)r has a %(f1)s that is greater than %(f2)s. This is not allowed; it would mean the line had a negative duration. No further points will not be generated along this track.') % {u'trackIDField': trackIDField, u'trackID': str(trackID), u'oid': linesTable.OIDFieldName, u'value': selectCursor.GetOID(), u'f1': startTimeField, u'f2': endTimeField}) 
     628                                trackIsValid = False 
     629                                continue 
     630 
     631                            # Divide the length of this line by its 
     632                            # duration to get the rate of travel. Then 
     633                            # multiply this by the temporal distance 
     634                            # to the next point to get the spatial 
     635                            # distance along this line to that point. 
     636 
     637                            rate = cls._GetLineLength(linesSR, unit, selectCursor, isTemporal) / (duration.microseconds * 0.000001 + duration.seconds + duration.days * 86400.) 
     638                            spatialDistanceToNextPoint = rate * (distanceToNextPoint.microseconds * 0.000001 + distanceToNextPoint.seconds + distanceToNextPoint.days * 86400.) 
     639 
     640                            Logger.Debug('Track %r: Read new line: rate = %r, spatial distance to next point = %r' % (trackID, rate, spatialDistanceToNextPoint)) 
     641 
     642                        # Iterate through the line's segments. 
     643 
     644                        geometry = selectCursor.GetGeometry() 
     645                        isMultipart = geometry.GetGeometryName().lower() != u'linestring' 
     646                        partIndex = 0 
     647                        endX = None 
     648                        endY = None 
     649                         
     650                        while not isMultipart or isMultipart and partIndex < geometry.GetGeometryCount(): 
     651                            if not isMultipart: 
     652                                part = geometry 
     653                            else: 
     654                                part = geometry.GetGeometryRef(partIndex) 
     655 
     656                            for i in range(1, part.GetPointCount()): 
     657 
     658                                # If the caller wants points at the 
     659                                # ends of the track and we have not 
     660                                # inserted the first point yet, do it. 
     661 
     662                                if startOfTrack and pointsAtLineEnds: 
     663                                    cls._InsertPointForLine(part.GetX(i-1), part.GetY(i-1), startTime, insertCursor, destFields, fieldValues) 
     664                                 
     665                                startOfTrack = False 
     666 
     667                                currentX = part.GetX(i-1) 
     668                                currentY = part.GetY(i-1) 
     669                                endX = part.GetX(i) 
     670                                endY = part.GetY(i) 
     671 
     672                                while True: 
     673                                    distanceToEnd = cls._GetDistance(currentX, currentY, endX, endY, linesSR, unit, isTemporal) 
     674                                     
     675                                    if not isTemporal: 
     676                                        if distanceToEnd < distanceToNextPoint: 
     677                                            distanceSincePoint += distanceToEnd 
     678                                            distanceToNextPoint -= distanceToEnd 
     679                                            break 
     680 
     681                                        pointX, pointY = cls._GetNextPointCoords(currentX, currentY, endX, endY, distanceToNextPoint, linesSR, unit, isTemporal) 
     682                                        cls._InsertPointForLine(pointX, pointY, None, insertCursor, destFields, fieldValues) 
     683 
     684                                        currentX = pointX 
     685                                        currentY = pointY 
     686 
     687                                        distanceSincePoint = 0 
     688                                        if distributeRemainder: 
     689                                            distanceToNextPoint = perTrackInterval[trackID] 
     690                                        else: 
     691                                            distanceToNextPoint = interval 
     692 
     693                                    else: 
     694                                        if distanceToEnd < spatialDistanceToNextPoint: 
     695                                            distanceSincePoint += datetime.timedelta(seconds=distanceToEnd / rate) 
     696                                            distanceToNextPoint -= datetime.timedelta(seconds=distanceToEnd / rate) 
     697                                            spatialDistanceToNextPoint -= distanceToEnd 
     698                                            break 
     699 
     700                                        pointX, pointY = cls._GetNextPointCoords(currentX, currentY, endX, endY, spatialDistanceToNextPoint, linesSR, unit, isTemporal) 
     701                                        cls._InsertPointForLine(pointX, pointY, endTime - datetime.timedelta(seconds=distanceToEnd / rate) + distanceToNextPoint, insertCursor, destFields, fieldValues) 
     702 
     703                                        currentX = pointX 
     704                                        currentY = pointY 
     705 
     706                                        distanceSincePoint = datetime.timedelta(0) 
     707                                        if distributeRemainder: 
     708                                            distanceToNextPoint = perTrackInterval[trackID] 
     709                                        else: 
     710                                            distanceToNextPoint = interval 
     711                                        spatialDistanceToNextPoint = rate * (distanceToNextPoint.microseconds * 0.000001 + distanceToNextPoint.seconds + distanceToNextPoint.days * 86400.) 
     712 
     713                            if not isMultipart: 
     714                                break 
     715                            partIndex += 1 
     716 
     717                    # If the caller requested that we place lines at 
     718                    # the end of tracks, and we have not done so for 
     719                    # the last track, insert the end point. 
     720                     
     721                    if pointsAtLineEnds and trackIsValid and (isTemporal and distanceSincePoint > datetime.timedelta(0) or not isTemporal and distanceSincePoint > 0) and endX is not None: 
     722                        Logger.Debug('Track %r: Writing end point' % trackID) 
     723                        cls._InsertPointForLine(endX, endY, endTime, insertCursor, destFields, fieldValues) 
     724 
     725                finally: 
     726                    del selectCursor 
     727            finally: 
     728                del insertCursor 
    345729        except: 
    346730            Logger.LogExceptionAsError() 
    347731            raise 
     732 
     733    @classmethod 
     734    def _InsertPointForLine(cls, pointX, pointY, pointTime, insertCursor, destFields, fieldValues): 
     735        from GeoEco.Datasets import CollectibleObject 
     736        insertCursor.SetGeometry(CollectibleObject._ogr().CreateGeometryFromWkt('POINT(%r %r)' % (pointX, pointY))) 
     737        for field in destFields: 
     738            insertCursor.SetValue(field, fieldValues[field]) 
     739        if pointTime is not None: 
     740            insertCursor.SetValue(u'PointTime', pointTime) 
     741        insertCursor.InsertRow() 
     742 
     743    @classmethod 
     744    def _GetLineLength(cls, linesSR, unit, selectCursor, isTemporal): 
     745        lengthInMeters = 0. 
     746        geometry = selectCursor.GetGeometry() 
     747        isMultipart = geometry.GetGeometryName().lower() != u'linestring' 
     748        partIndex = 0 
     749 
     750        while not isMultipart or isMultipart and partIndex < geometry.GetGeometryCount(): 
     751            if not isMultipart: 
     752                part = geometry 
     753            else: 
     754                part = geometry.GetGeometryRef(partIndex) 
     755 
     756            # If it is it is a geographic coordinate system and the 
     757            # unit is linear, compute the length of this part using 
     758            # the haversine formula. 
     759 
     760            if not linesSR.IsProjected() and unit != 'decimal degrees' and not isTemporal: 
     761                for i in range(1, part.GetPointCount()): 
     762                    lengthInMeters += cls._HaversineDistance(part.GetX(i), part.GetY(i), part.GetX(i-1), part.GetY(i-1)) 
     763 
     764            # Otherwise we can just sum up the Euclidean distance of 
     765            # the part's segements. OGR provides a convenient function 
     766            # for this. 
     767 
     768            else: 
     769                if not linesSR.IsProjected(): 
     770                    lengthInMeters += part.Length() 
     771                else: 
     772                    lengthInMeters += part.Length() * linesSR.GetLinearUnits() 
     773 
     774            if not isMultipart: 
     775                break 
     776            partIndex += 1 
     777 
     778        return lengthInMeters 
     779 
     780    @classmethod 
     781    def _GetDistance(cls, x1, y1, x2, y2, linesSR, unit, isTemporal): 
     782        if not linesSR.IsProjected() and unit != 'decimal degrees' and not isTemporal: 
     783            return cls._HaversineDistance(x1, y1, x2, y2) 
     784        if not linesSR.IsProjected(): 
     785            return ((x2 - x1)**2 + (y2 - y1)**2)**0.5 
     786        return ((x2 - x1)**2 + (y2 - y1)**2)**0.5 * linesSR.GetLinearUnits() 
     787 
     788    @classmethod 
     789    def _GetNextPointCoords(cls, currentX, currentY, endX, endY, distanceToNextPoint, linesSR, unit, isTemporal): 
     790 
     791        # If it is it is a geographic coordinate system and the unit 
     792        # is linear, we have to perform this calculation on a great 
     793        # circle. First calculate the forward azimuth (the initial 
     794        # bearing) toward the end point. 
     795 
     796        if not linesSR.IsProjected() and unit != 'decimal degrees' and not isTemporal: 
     797            lat1 = math.radians(currentY) 
     798            lat2 = math.radians(endY) 
     799            dlon = math.radians(endX - currentX) 
     800 
     801            y = math.sin(dlon) * math.cos(lat2) 
     802            x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dlon) 
     803            azimuth = math.atan2(y, x) 
     804 
     805            # Now compute the coordinates of the next point, traveling 
     806            # the specified distance on the forward azimuth. 
     807 
     808            distRadians = distanceToNextPoint / 6371009     # 6371009 is mean radius of Earth, in meters 
     809 
     810            lon1 = math.radians(currentX) 
     811 
     812            lat2 = math.asin(math.sin(lat1) * math.cos(distRadians) + math.cos(lat1) * math.sin(distRadians) * math.cos(azimuth)) 
     813            lon2 = lon1 + math.atan2(math.sin(azimuth) * math.sin(distRadians) * math.cos(lat1), math.cos(distRadians) - math.sin(lat1) * math.sin(lat2)) 
     814            lon2 = (lon2 + 3 * math.pi) % (2 * math.pi) - math.pi;      # Normalize to -180 to +180 
     815 
     816            return math.degrees(lon2), math.degrees(lat2) 
     817 
     818        # Otherwise we can perform this calculation using basic 
     819        # trigonometry. 
     820 
     821        proportion = distanceToNextPoint / (((currentX - endX)**2 + (currentY - endY)**2)**0.5) 
     822        return currentX + (endX - currentX)*proportion, currentY + (endY - currentY)*proportion 
     823 
     824    @classmethod 
     825    def _HaversineDistance(cls, lon1, lat1, lon2, lat2): 
     826        lon1, lat1, lon2, lat2 = map(math.radians, [lon1, lat1, lon2, lat2]) 
     827        dlon = lon2 - lon1 
     828        dlat = lat2 - lat1 
     829        a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2 
     830        c = 2 * math.asin(math.sqrt(a)) 
     831        return 6371009 * c      # 6371009 is mean radius of Earth, in meters 
    348832 
    349833    @classmethod 
     
    6391123            nearAreProjected = nearFeaturesDescribe.SpatialReference.ProjectionCode != 0 
    6401124 
    641         # Validate that either the points or the near features are 
     1125        # Warn the user if neither the points or the near features are 
    6421126        # projected. 
    6431127 
    6441128        if not pointsAreProjected and not nearAreProjected: 
    645             Logger.RaiseException(ValueError(_(u'Neither the point features (%(points)s) nor the near features (%(near)s) use a projected coordinate system. One or the other of these must use a projected coordinate system. Please project one or the other of them and try again.') % {u'points': pointFeatures, u'near': nearFeatures})) 
     1129            Logger.Warning(_(u'Neither the point features (%(points)s) nor the near features (%(near)s) use a projected coordinate system. Because ArcGIS finds the nearest feature in Euclidean distance, not geodetic distance, the features identified as nearest may not actually be the nearest geodetically.') % {u'points': pointFeatures, u'near': nearFeatures}) 
    6461130 
    6471131        # Copy the caller's points into the temp geodatabase, 
     
    17682252interval. If the lines are in a geographic coordinate system, points 
    17692253will be placed along the great circles that connect the lines' 
    1770 vertices and spaced according to geodesic distance computed on the 
    1771 coordiante system's ellipsoid. Because of this, the points will not 
    1772 occur exactly along the lines unless the lines are themselves great 
    1773 circles (i.e. they exactly follow the equator or a meridian). 
     2254vertices and spaced according to haversine distance. Because of this, 
     2255the points will not occur exactly along the lines unless the lines are 
     2256themselves great circles (i.e. they exactly follow the equator or a 
     2257meridian). 
    17742258 
    17752259If a temporal unit is used, the input lines may be in a projected or