Exemplo n.º 1
0
class Place(AbstractNode, MP_Node):
    """
    Represents a Place with name, used for geocoding
    https://developers.arcgis.com/rest/geocode/api-reference/geocoding-category-filtering.htm
    Extending treebeard MP_Node provides the hierarchy
    """
    verified = models.BooleanField(default=False)  # true if a user has verified this place, ie by editing it manually
    extras = ExtrasDotField(default='')  # a dictionary of name/value pairs to support flexible extra metadata
    geometry = ExtrasDotField(default='')  # a dictionary of name/value pairs to support geometry

    node_order_by = ['name']  # for treebeard ordering
Exemplo n.º 2
0
class AbstractOperation(models.Model):
    """Represents an area where a team is operating.  Could be a regular
    station posting, an incident, an exercise, or whatever makes sense.
    For a discussion of incident file naming conventions see
    http://gis.nwcg.gov/2008_GISS_Resource/student_workbook/unit_lessons/Unit_08_File_Naming_Review.pdf"""

    folders = models.ManyToManyField(Folder, related_name='%(app_label)s_%(class)s_folders')
    name = models.CharField(max_length=32, blank=True,
                            help_text="A descriptive name for this operation.  Example: 'beaver_pond'.")
    operationId = models.CharField(max_length=32, blank=True, verbose_name='operation id',
                                   help_text="A formal id for this operation.  For incidents, use the incident number.  Example: 'PA-DEWA-0001'")
    minTime = models.DateTimeField(blank=True, null=True, verbose_name='start date')
    maxTime = models.DateTimeField(blank=True, null=True, verbose_name='end date')
    minLat = models.FloatField(blank=True, null=True, verbose_name='minimum latitude')  # WGS84 degrees
    minLon = models.FloatField(blank=True, null=True, verbose_name='minimum longitude')  # WGS84 degrees
    maxLat = models.FloatField(blank=True, null=True, verbose_name='maximum latitude')  # WGS84 degrees
    maxLon = models.FloatField(blank=True, null=True, verbose_name='maximum longitude')  # WGS84 degrees
    notes = models.TextField(blank=True)
    tags = TagField(blank=True)
    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")
    objects = AbstractModelManager(parentModel=None)

    class Meta:
        abstract = True

    def __unicode__(self):
        return '%s %s %s' % (self.__class__.__name__, self.name, self.operationId)
Exemplo n.º 3
0
class MapLayer(AbstractMap):
    """ A map layer which will have a collection of features that have content in them. """
    jsonFeatures = ExtrasDotField()
    defaultColor = models.CharField(max_length=32, null=True, blank=True)

    def getEditHref(self):
        return reverse('mapEditLayer', kwargs={'layerID': self.uuid})

    def toDict(self):
        result = modelToDict(self)
        result['uuid'] = self.uuid
        return result

    def getTreeJson(self):
        """ Get the json block that the fancy tree needs to render this node """
        result = super(MapLayer, self).getTreeJson()
        result["data"]["layerJSON"] = reverse('mapLayerJSON',
                                              kwargs={'layerID': self.uuid})
        return result

    def getGoogleEarthUrl(self, request):
        return request.build_absolute_uri(
            reverse('mapLayerKML', kwargs={'layerID': self.uuid}))

    def getFeatureJson(self):
        return self.jsonFeatures
Exemplo n.º 4
0
class Assignment(models.Model):
    operation = models.ForeignKey(Operation)
    userProfile = models.ForeignKey('UserProfile')
    organization = models.CharField(max_length=64, blank=True, help_text="The organization or unit you are assigned to for this operation.")
    jobTitle = models.CharField(max_length=64, blank=True, help_text="Your job title for this operation.")
    contactInfo = models.CharField(max_length=128, blank=True, help_text="Your contact info for this operation. Cell phone number is most important.")
    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")
Exemplo n.º 5
0
class IconStyle(UuidModel):
    name = models.CharField(max_length=40, blank=True)
    url = models.CharField(max_length=1024, blank=True)
    width = models.PositiveIntegerField(default=0)
    height = models.PositiveIntegerField(default=0)
    scale = models.FloatField(default=1)
    color = models.CharField(
        max_length=16,
        blank=True,
        help_text='Optional KML color specification, hex in AABBGGRR order')
    extras = ExtrasDotField()

    def __unicode__(self):
        return '%s %s' % (self.__class__.__name__, self.name)

    def writeKml(self, out, heading=None, urlFn=None, color=None):
        if not color:
            color = self.color
            try:
                int(self.color)
            except:
                try:
                    color = self.color()
                except:
                    color = self.color
        if color:
            colorStr = '<color>%s</color>' % color
        else:
            colorStr = ''
        if self.scale != 1:
            scaleStr = '<scale>%s</scale>' % self.scale
        else:
            scaleStr = ''
        if heading is not None:
            headingStr = '<heading>%s</heading>' % heading
        else:
            headingStr = ''
        imgUrl = self.url
        if urlFn:
            imgUrl = urlFn(imgUrl)
        out.write("""
<IconStyle>
  %(headingStr)s
  %(colorStr)s
  %(scaleStr)s
  <Icon>
    <href>%(url)s</href>
  </Icon>
</IconStyle>
""" % dict(url=imgUrl,
           scaleStr=scaleStr,
           colorStr=colorStr,
           headingStr=headingStr,
           id=self.pk))
Exemplo n.º 6
0
class AutomatchResults(models.Model):
    issMRF = models.CharField(max_length=255, unique=True, help_text="Please use the following format: <em>[Mission ID]-[Roll]-[Frame number]</em>") 
    matchedImageId = models.CharField(max_length=255, blank=True)
    matchConfidence = models.CharField(max_length=255, blank=True)
    matchDate = models.DateTimeField(null=True, blank=True)
    capturedTime = models.DateTimeField(null=True, blank=True)
    centerPointSource = models.CharField(max_length=255, blank=True, help_text="source of center point. Either curated, CEO, GeoSens, or Nadir")
    centerLat = models.FloatField(null=True, blank=True, default=0)
    centerLon = models.FloatField(null=True, blank=True, default=0) 
    registrationMpp = models.FloatField(null=True, blank=True, default=0)
    extras = ExtrasDotField() # stores tie point pairs
    metadataExportName = models.CharField(max_length=255,
                                          null=True, blank=True)
    metadataExport = models.FileField(upload_to=getNewExportFileName,
                                      max_length=255,
                                      null=True, blank=True)
    writtenToFile = models.BooleanField(default=False)
Exemplo n.º 7
0
class MapLayer(AbstractMap):
    """ A map layer which will have a collection of features that have content in them. """
    jsonFeatures = ExtrasDotField()
    defaultColor = models.CharField(max_length=32, null=True, blank=True)

    def getEditHref(self):
        return reverse('mapEditLayer', kwargs={'layerID': self.uuid})

    def toDict(self):
        result = modelToDict(self)
        result['uuid'] = self.uuid
        return result

    # A bit hacky, but... toMapDict() returns metadata info on the layer object so getMappedObjectsJson() can be used
    # to return a summary of the available layers if you pass it (e.g.) xgds_map_server.MapLayer as a param.
    def toMapDict(self):
        result = {"maxLat": self.maxLat, "minLat": self.minLat,
                  "maxLon": self.maxLon, "minLon": self.minLon,
                  "parent": self.parent.uuid, "creator":self.creator,
                  "defaultColor": self.defaultColor,
                  "description": self.description,
                  "creation_time": self.creation_time,
                  "uuid": self.uuid, "visible": self.visible,
                  "modification_time": self.modification_time,
                  "region": self.region,
                  "name": self.name}
        return result

    def get_tree_json(self):
        """ Get the json block that the fancy tree needs to render this node """
        result = super(MapLayer, self).get_tree_json()
        result["data"]["layerJSON"] = reverse('mapLayerJSON', kwargs={'layerID': self.uuid})
        return result
    
    def getGoogleEarthUrl(self, request):
        theUrl = reverse('mapLayerKML', kwargs={'layerID': self.uuid})
        theUrl = insertIntoPath(theUrl, 'rest')
        return request.build_absolute_uri(theUrl)

    def getFeatureJson(self):
        return self.jsonFeatures

    def getKmlUrl(self):
        """ If this element has an url which returns kml, override this function to return that url. """
        return reverse('mapLayerKML', kwargs={'layerID': self.uuid})
Exemplo n.º 8
0
class GroupProfile(models.Model):
    group = models.OneToOneField(Group, help_text='Reference to corresponding Group object of built-in Django authentication system.')
    context = models.ForeignKey(Context, blank=True, null=True,
                                help_text='Default context associated with this group.')

    password = models.CharField(max_length=128, blank=True, null=True)

    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")

    def password_required(self):
        return (self.password is None)

    def set_password(self, raw_password):
        # Make sure the password field isn't set to none
        if raw_password is not None:
            import hashlib
            import random

            # Compute the differnt parts we'll need to secure store the password
            algo = 'sha1'
            salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
            hsh = hashlib.sha1(salt + raw_password).hexdigest()

            # Set the password value
            self.password = '******' % (algo, salt, hsh)

    def authenticate(self, user_password):
        # Make sure the password field isn't set to none
        if self.password is not None:
            import hashlib

            # Get the parts of the group password
            parts = self.password.split('$')
            _algo, salt, hsh = parts[:2]

            # Compute the hash of the user password
            #user_hsh = get_hexdigest(algo, salt, user_password)
            user_hash = hashlib.sha1(salt + user_password).hexdigest()

            # Retrun the resulting comparison
            return (hsh == user_hash)

        else:
            return True
Exemplo n.º 9
0
class Context(models.Model):
    """
    A context is a collection of settings that it makes sense to share
    between members of a group or people on an incident.
    """
    name = models.CharField(max_length=40,
                            help_text="A descriptive name.")
    uploadFolder = models.ForeignKey(Folder,
                                     related_name='%(app_label)s_%(class)s_uploadingContextSet',
                                     help_text="Folder to upload data to.")
    timeZone = models.CharField(max_length=32,
                                choices=TIME_ZONE_CHOICES,
                                default=DEFAULT_TIME_ZONE,
                                help_text="Time zone used to display timestamps and to interpret incoming timestamps that aren't labeled with a time zone.")
    layerConfigUrl = models.CharField(max_length=256,
                                      help_text='URL pointing to a JSON config file specifying what layers to show')
    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")
Exemplo n.º 10
0
class Sensor(models.Model):
    name = models.CharField(max_length=40, blank=True,
                            help_text='Your name for the instrument. Example: "MicroImager" or "GeoCam"')
    make = models.CharField(max_length=40, blank=True,
                            help_text='The organization that makes the sensor.  Example: "Canon"')
    model = models.CharField(max_length=40, blank=True,
                             help_text='The model of sensor.  Example: "Droid" or "PowerShot G9"')
    software = models.CharField(max_length=160, blank=True,
                                help_text='Software running on the sensor, including any known firmware and version details. Example: "GeoCam Mobile 1.0.10, Android firmware 2.1-update1 build ESE81"')
    serialNumber = models.CharField(max_length=80, blank=True,
                                    verbose_name='serial number',
                                    help_text='Information that uniquely identifies this particular sensor unit. Example: "serialNumber:HT851N002808 phoneNumber:4126573579" ')
    notes = models.TextField(blank=True)
    tags = TagField(blank=True)
    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")

    def __unicode__(self):
        return self.name
Exemplo n.º 11
0
class AbstractUserProfile(models.Model):
    """
    Adds some extended fields to the django built-in User type.
    """
    user = models.OneToOneField(User, help_text='Reference to corresponding User object of built-in Django authentication system.', related_name='%(app_label)s_%(class)s')
    displayName = models.CharField(max_length=40, blank=True,
                                   help_text="The 'uploaded by' name that will appear next to data you upload.  Defaults to 'F. Last', but if other members of your unit use your account you might want to show your unit name instead.")
    homeOrganization = models.CharField(max_length=64, blank=True, help_text="The home organization you usually work for.")
    homeJobTitle = models.CharField(max_length=64, blank=True, help_text="Your job title in your home organization.")
    contactInfo = models.CharField(max_length=128, blank=True, help_text="Your contact info in your home organization.")
    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")

    class Meta:
        ordering = ['user']
        abstract = True

    def __unicode__(self):
        return u'<User %s "%s %s">' % (self.user.username, self.user.first_name, self.user.last_name)
Exemplo n.º 12
0
class LineStyle(UuidModel):
    name = models.CharField(max_length=40, blank=True)
    color = models.CharField(
        max_length=16,
        blank=True,
        help_text='Optional KML color specification, hex in AABBGGRR order')
    width = models.PositiveIntegerField(default=1, null=True, blank=True)
    extras = ExtrasDotField()

    def __unicode__(self):
        return '%s %s' % (self.__class__.__name__, self.name)

    def writeKml(self, out, urlFn=None, color=None):
        if not color:
            color = self.color
        if color:
            colorStr = '<color>%s</color>' % color
        else:
            colorStr = ''
        if self.width is not None:
            widthStr = '<width>%s</width>' % self.width
        else:
            widthStr = ''
        out.write("""
<LineStyle>
  %(colorStr)s
  %(widthStr)s
</LineStyle>
""" % dict(colorStr=colorStr, widthStr=widthStr))

    def getAlpha(self):
        """ Get 0-1 alpha value from color """
        if self.color:
            decvalue = int("0x" + self.color[0:2], 16)
            return decvalue / 255
        return 1.0

    def getHexColor(self):
        if self.color:
            return self.color[2:]
        return None
Exemplo n.º 13
0
class Feature(models.Model):
    folders = models.ManyToManyField(Folder, related_name='%(app_label)s_%(class)s_set', blank=True)
    name = models.CharField(max_length=80, blank=True, default='')
    author = models.ForeignKey(User, null=True, related_name='%(app_label)s_%(class)s_authoredSet',
                               help_text='The user who collected the data (when you upload data, Share tags you as the author)')
    sensor = models.ForeignKey(Sensor, blank=True, null=True, related_name='%(app_label)s_%(class)s_set')
    isAerial = models.BooleanField(default=False, blank=True, verbose_name='aerial data', help_text="True for aerial data. Generally for non-aerial data we snap to terrain in 3D visualizations so that GPS errors can't cause features to be rendered underground.")
    notes = models.TextField(blank=True)
    tags = TagField(blank=True)
    icon = models.CharField(max_length=16, blank=True)

    # these fields help us handle changes to data products
    status = models.CharField(max_length=1, choices=STATUS_CHOICES,
                              default=STATUS_CHOICES[0][0])
    processed = models.BooleanField(default=False)
    version = models.PositiveIntegerField(default=0)
    purgeTime = models.DateTimeField(null=True, blank=True)
    workflowStatus = models.PositiveIntegerField(choices=WORKFLOW_STATUS_CHOICES,
                                                 default=DEFAULT_WORKFLOW_STATUS)
    mtime = models.DateTimeField(null=True, blank=True)

    uuid = UuidField()
    extras = ExtrasDotField(help_text="A place to add extra fields if we need them but for some reason can't modify the table schema.  Expressed as a JSON-encoded dict.")

    objects = AbstractModelManager(parentModel=None)
    viewerExtension = None  # override in derived classes

    class Meta:
        abstract = True

    def save(self, **kwargs):
        self.mtime = datetime.datetime.now()
        super(Feature, self).save(**kwargs)

    def deleteFiles(self):
        shutil.rmtree(self.getDir(), ignore_errors=True)

    def getCachedField(self, field):
        relatedId = getattr(self, '%s_id' % field)
        key = 'fieldCache-geocamCore-Feature-%s-%d' % (field, relatedId)
        result = cache.get(key)
        if not result:
            result = getattr(self, field)
            cache.set(key, result)
        return result

    def getCachedFolder(self):
        return self.getCachedField('folder')

    def getCachedAuthor(self):
        return self.getCachedField('author')

    def utcToLocalTime(self, dtUtc0):
        dtUtc = pytz.utc.localize(dtUtc0)
        # TODO: respect user's context time zone
        localTz = pytz.timezone(settings.TIME_ZONE)
        dtLocal = dtUtc.astimezone(localTz)
        return dtLocal

    def getAuthor(self):
        pass

    def __unicode__(self):
        return '%s %d %s %s %s' % (self.__class__.__name__, self.id, self.name or '[untitled]', self.author.username, self.uuid)

    def getDirSuffix(self, version=None):
        if version is None:
            version = self.version
        idStr = str(self.id) + 'p'
        idList = [idStr[i:(i + 2)] for i in xrange(0, len(idStr), 2)]
        return [self.__class__.__name__.lower()] + idList + [str(version)]

    def getDir(self, version=None):
        return os.path.join(settings.DATA_DIR, *self.getDirSuffix(version))

    def getIconDict(self, kind=''):
        return dict(url=getIconUrl(self.icon + kind),
                    size=getIconSize(self.icon + kind))

    def getStyledIconDict(self, kind='', suffix=''):
        return dict(normal=self.getIconDict(kind + suffix),
                    highlighted=self.getIconDict(kind + 'Highlighted' + suffix))

    def getUserDisplayName(self, user):
        if user.last_name == 'group':
            return user.first_name
        else:
            return '%s %s' % (user.first_name.capitalize(),
                              user.last_name.capitalize())

    def getViewerUrl(self):
        name = self.name or 'untitled' + self.viewerExtension
        return ('%s%s/%s/%s/%s'
                % (settings.SCRIPT_NAME,
                   self.__class__._meta.app_label,
                   self.__class__.__name__.lower(),
                   self.id,
                   name))

    def getEditUrl(self):
        return ('%s%s/%s/%s/%s/'
                % (settings.SCRIPT_NAME,
                   self.__class__._meta.app_label,
                   'editWidget',
                   self.__class__.__name__.lower(),
                   self.id))

    def getProperties(self):
        tagsList = tagging.utils.parse_tag_input(self.tags)
        author = self.getCachedAuthor()
        authorDict = dict(userName=author.username,
                          displayName=self.getUserDisplayName(author))
        return dict(name=self.name,
                    version=self.version,
                    isAerial=self.isAerial,
                    author=authorDict,
                    notes=self.notes,
                    tags=tagsList,
                    icon=self.getStyledIconDict(),
                    localId=self.id,
                    subtype=self.__class__.__name__,
                    viewerUrl=self.getViewerUrl(),
                    editUrl=self.getEditUrl(),
                    )

    def cleanDict(self, d):
        return dict(((k, v)
                     for k, v in d.iteritems()
                     if v not in (None, '')))

    def getGeometry(self):
        return None  # override in derived classes

    def getGeoJson(self):
        return dict(type='Feature',
                    id=self.uuid,
                    geometry=self.getGeometry(),
                    properties=self.cleanDict(self.getProperties()))

    def getDirUrl(self):
        return '/'.join([settings.DATA_URL] + list(self.getDirSuffix()))
Exemplo n.º 14
0
class AbstractTrack(SearchableModel, UuidModel, HasVehicle, HasFlight):
    """ This is for an abstract track with a FIXED vehicle model, ie all the tracks have like vehicles.
    """
    name = models.CharField(max_length=40, blank=True)
    iconStyle = 'set this to DEFAULT_ICON_STYLE_FIELD() or similar in derived classes'
    lineStyle = 'set this to DEFAULT_LINE_STYLE_FIELD() or similar in derived classes'
    extras = ExtrasDotField()

    def __init__(self, *args, **kwargs):
        self.coordGroups = []
        self.timesGroups = []
        super(AbstractTrack, self).__init__(*args, **kwargs)
        if self.id:
            self.pastposition_set = PAST_POSITION_MODEL.get().objects.filter(
                track_id=self.id)

    class Meta:
        abstract = True
        ordering = ('name', )

    def __unicode__(self):
        return '%s %s' % (self.__class__.__name__, self.name)

    def getTimezone(self):
        """
        Override if your model has a different way of getting time zones.
        It would be nifty to look them up from lat/lon
        """
        return pytz.timezone(settings.TIME_ZONE)

    def getPositions(self, downsample=False):
        result = PAST_POSITION_MODEL.get().objects.filter(track=self)
        if downsample:
            result = downsample_queryset(
                result, settings.GEOCAM_TRACK_DOWNSAMPLE_POSITIONS_SECONDS)
        return result

    def getCurrentPositions(self):
        return POSITION_MODEL.get().objects.filter(track=self)

    def getLabelName(self, pos):
        return self.name

    def getLabelExtra(self, pos):
        return ''

    @property
    def lat(self):
        # This is a total hack to get tracks to show on the map after they were searched.
        return 1

    def get_tree_json(self):
        vehicle_name = ""
        if hasattr(self, 'vehicle') and self.vehicle:
            vehicle_name = self.vehicle.name

        result = {
            "title":
            self.name,
            "key":
            self.uuid,
            "tooltip":
            "%s for %s" % (settings.GEOCAM_TRACK_TRACK_MONIKER, self.name),
            "data": {
                "json":
                reverse('geocamTrack_mapJsonTrack_downsample',
                        kwargs={'uuid': str(self.uuid)}),
                "kmlFile":
                reverse('geocamTrack_trackKml',
                        kwargs={'trackName': self.name}),
                "sseUrl":
                "",
                "type":
                'MapLink',
                "vehicle":
                vehicle_name
            }
        }

        return result

    def getIconStyle(self, pos):
        if hasattr(self, '_currentIcon'):
            return self._currentIcon

        # use specific style if given
        if self.iconStyle is not None:
            self._currentIcon = self.iconStyle
            return self.iconStyle

        # use pointer icon if we know the heading
        if pos.heading is not None:
            if not hasattr(self, '_pointerIcon'):
                self._pointerIcon = IconStyle.objects.get(name='pointer')
            self._currentIcon = self._pointerIcon
            return self._pointerIcon

        # use spot icon otherwise
        if not hasattr(self, '_defaultIcon'):
            self._defaultIcon = IconStyle.objects.get(name='default')
            self._defaultIcon.color = self.getLineStyleColor
            self._currentIcon = self._defaultIcon

        return self._currentIcon

    def getLineStyle(self):
        if self.lineStyle:
            return self.lineStyle
        return LineStyle.objects.get(name='default')

    def getLineStyleColor(self):
        if self.lineStyle:
            return self.lineStyle.color
        return LineStyle.objects.get(name='default').color

    def getIconColor(self, pos):
        try:
            currentIconStyle = self.getIconStyle(pos)
            int(str(currentIconStyle.color))
            return currentIconStyle.color
        except:
            try:
                return currentIconStyle.color()
            except:
                return str(currentIconStyle.color)

    def getLineColor(self):
        return self.getLineStyle().color

    def getKmlUrl(self, **kwargs):
        kwargs['trackName'] = self.name
        return getKmlUrl(**kwargs)

    def writeCurrentKml(self, out, pos, iconStyle=None, urlFn=None):
        if iconStyle is None:
            iconStyle = self.getIconStyle(pos)
        ageStr = ''
        if settings.GEOCAM_TRACK_SHOW_CURRENT_POSITION_AGE:
            now = datetime.datetime.now(pytz.utc)
            diff = now - pos.timestamp
            diffSecs = diff.days * 24 * 60 * 60 + diff.seconds
            if diffSecs >= settings.GEOCAM_TRACK_CURRENT_POSITION_AGE_MIN_SECONDS:
                age = TimeUtil.getTimeShort(pos.timestamp)
                ageStr = ' (%s)' % age
            ageStr += ' %s' % getTimeSpinner(datetime.datetime.now(pytz.utc))

        label = ('%s%s%s' %
                 (self.getLabelName(pos), self.getLabelExtra(pos), ageStr))
        out.write("""
<Placemark>
  <name>%(label)s</name>
""" % dict(label=label))
        if iconStyle:
            out.write("<Style>\n")
            iconStyle.writeKml(out,
                               pos.getHeading(),
                               urlFn=urlFn,
                               color=self.getIconColor(pos))
            out.write("</Style>\n")

        out.write("""
  <Point>
    <coordinates>
""")
        pos.writeCoordinatesKml(out)
        out.write("""
    </coordinates>
  </Point>
</Placemark>
""")

    def writeCompassKml(self, out, pos, urlFn=None):
        pngUrl = settings.STATIC_URL + 'geocamTrack/icons/compassRoseLg.png'
        if urlFn:
            pngUrl = urlFn(pngUrl)
        out.write("""
<Placemark>
  <Style>
    <IconStyle>
      <scale>6.0</scale>
      <Icon>
        <href>%(pngUrl)s</href>
      </Icon>
    </IconStyle>
  </Style>
  <Point>
    <coordinates>
""" % {'pngUrl': pngUrl})
        pos.writeCoordinatesKml(out)
        out.write("""
    </coordinates>
  </Point>
</Placemark>
""")

    def writeAnimatedPlacemarks(self, out, positions):
        out.write("    <Folder>\n")
        out.write("        <name>Trajectory</name>\n")
        out.write("        <open>0</open>\n")

        #         out.write('      <visibility>1</visibility>\n')
        numPositions = len(positions) - 1
        for i, pos in enumerate(positions):
            # start new line string
            out.write("        <Placemark>\n")
            out.write("            <TimeSpan>\n")
            begin = pytz.utc.localize(pos.timestamp).astimezone(
                self.getTimezone())
            tzoffset = begin.strftime('%z')
            tzoffset = tzoffset[0:-2] + ":00"
            out.write(
                "                <begin>%04d-%02d-%02dT%02d:%02d:%02d%s</begin>\n"
                % (begin.year, begin.month, begin.day, begin.hour,
                   begin.minute, begin.second, tzoffset))
            if i < numPositions:
                nextpos = positions[i + 1]
                end = pytz.utc.localize(nextpos.timestamp).astimezone(
                    self.getTimezone())
                # end = self.getTimezone().localize(nextpos.timestamp)
            else:
                end = begin

            out.write(
                "                <end>%04d-%02d-%02dT%02d:%02d:%02d%s</end>\n"
                % (end.year, end.month, end.day, end.hour, end.minute,
                   end.second, tzoffset))
            out.write("            </TimeSpan>\n")
            #             out.write("            <styleUrl>#dw%d</styleUrl>\n" % (pos.heading))
            out.write("            <styleUrl>#%s</styleUrl>\n" %
                      self.getIconStyle(pos).pk)
            out.write(
                "            <gx:balloonVisibility>1</gx:balloonVisibility>\n")
            out.write("            <Point>\n")
            out.write("                <coordinates>")
            pos.writeCoordinatesKml(out)
            out.write("                </coordinates>\n")
            out.write("            </Point>\n")
            out.write("        </Placemark>\n")
        out.write("        </Folder>\n")

    def writeTrackKml(self,
                      out,
                      positions=None,
                      lineStyle=None,
                      urlFn=None,
                      animated=False):
        if positions is None:
            positions = self.getPositions()
        if lineStyle is None:
            lineStyle = self.lineStyle

        n = positions.count()
        if n == 0:
            return

        if n < 2:
            # kml LineString requires 2 or more positions
            return
        out.write("<Folder>\n")
        out.write("""
<Placemark>
  <name>%(name)s path</name>
""" % dict(name=self.name))
        if lineStyle:
            out.write("<Style>")
            lineStyle.writeKml(out, urlFn=urlFn, color=self.getLineColor())
            out.write("</Style>")

        if animated:
            if self.iconStyle:
                out.write("<Style id=\"%s\">\n" % self.iconStyle.pk)
                self.iconStyle.writeKml(out,
                                        0,
                                        urlFn=urlFn,
                                        color=self.getLineColor())
                out.write("</Style>\n")

        out.write("""
  <MultiGeometry>
    <LineString>
      <tessellate>1</tessellate>
      <coordinates>
""")
        lastPos = None
        breakDist = settings.GEOCAM_TRACK_START_NEW_LINE_DISTANCE_METERS
        for pos in positions:
            if lastPos and breakDist is not None:
                diff = geomath.calculateDiffMeters(
                    [lastPos.longitude, lastPos.latitude],
                    [pos.longitude, pos.latitude])
                dist = geomath.getLength(diff)
                if dist > breakDist:
                    # start new line string
                    out.write("""
      </coordinates>
    </LineString>
    <LineString>
      <tessellate>1</tessellate>
      <coordinates>
""")
            pos.writeCoordinatesKml(out)
            lastPos = pos

        out.write("""
      </coordinates>
    </LineString>
  </MultiGeometry>
</Placemark>
""")
        if animated:
            self.writeAnimatedPlacemarks(out, list(positions))
        out.write("</Folder>\n")

    def getInterpolatedPosition(self, utcDt):
        positions = PAST_POSITION_MODEL.get().objects.filter(track=self)

        # get closest position after utcDt
        afterPositions = positions.filter(
            timestamp__gte=utcDt).order_by('timestamp')
        if afterPositions.count():
            afterPos = afterPositions[0]
        else:
            return None
        afterDelta = timeDeltaTotalSeconds(afterPos.timestamp - utcDt)

        # special case -- if we have a position exactly matching utcDt
        if afterPos.timestamp == utcDt:
            return POSITION_MODEL.get().getInterpolatedPosition(
                utcDt, 1, afterPos, 0, afterPos)

        # get closest position before utcDt
        beforePositions = positions.filter(
            timestamp__lt=utcDt).order_by('-timestamp')
        if beforePositions.count():
            beforePos = beforePositions[0]
        else:
            return None
        beforeDelta = timeDeltaTotalSeconds(utcDt - beforePos.timestamp)
        delta = beforeDelta + afterDelta

        if delta > settings.GEOCAM_TRACK_INTERPOLATE_MAX_SECONDS:
            return None

        # interpolate
        beforeWeight = afterDelta / delta
        afterWeight = beforeDelta / delta
        return POSITION_MODEL.get().getInterpolatedPosition(
            utcDt, beforeWeight, beforePos, afterWeight, afterPos)

    @classmethod
    def cls_type(cls):
        return settings.GEOCAM_TRACK_TRACK_MONIKER

    @property
    def color(self):
        color = self.getLineStyle().getHexColor()
        if color:
            return color
        return None

    @property
    def alpha(self):
        return self.getLineStyle().getAlpha()

    @property
    def icon_url(self):
        if self.iconStyle:
            return self.iconStyle.url
        return None

    @property
    def icon_color(self):
        if self.iconStyle:
            return self.iconStyle.color
        return None

    @property
    def icon_scale(self):
        if self.iconStyle:
            return self.iconStyle.scale
        return 1

    def buildTimeCoords(self, downsample=False):

        currentPositions = self.getPositions(downsample)
        if currentPositions.count() < 2:
            return
        if self.coordGroups:
            return
        coords = []
        times = []

        lastPos = None
        breakDist = settings.GEOCAM_TRACK_START_NEW_LINE_DISTANCE_METERS
        for pos in currentPositions:
            if lastPos and breakDist is not None:
                diff = geomath.calculateDiffMeters(
                    [lastPos.longitude, lastPos.latitude],
                    [pos.longitude, pos.latitude])
                dist = geomath.getLength(diff)
                if dist > breakDist:
                    # start new line string
                    if coords:
                        self.coordGroups.append(coords)
                        coords = []
                        self.timesGroups.append(times)
                        times = []
                coords.append(pos.coords_array)
                times.append(pos.timestamp)
            lastPos = pos
        self.coordGroups.append(coords)
        self.timesGroups.append(times)

    @property
    def coords(self):
        self.buildTimeCoords()
        if self.coordGroups:
            return self.coordGroups
        return None

    @property
    def times(self):
        self.buildTimeCoords()
        if self.timesGroups:
            return self.timesGroups
        return None

    def toMapDict(self, downsample=False):
        result = super(AbstractTrack, self).toMapDict()
        result['coords_array_order'] = PAST_POSITION_MODEL.get(
        ).coords_array_order()
        if 'vehicle' in result:
            if self.vehicle:
                result['vehicle'] = self.vehicle.name
            else:
                del result['vehicle']
        self.buildTimeCoords(downsample)
        if self.timesGroups:
            result['times'] = self.timesGroups

        if self.coordGroups:
            result['coords'] = self.coordGroups

        return result

    @classmethod
    def timesearchField(cls):
        return None

    @property
    def event_time(self):
        self.buildTimeCoords()
        if self.timesGroups:
            return self.timesGroups[0][0]
        return None

    @classmethod
    def getSearchFormFields(cls):
        return ['name', 'vehicle']
Exemplo n.º 15
0
class AbstractPlan(models.Model):
    uuid = UuidField(unique=True, db_index=True)
    name = models.CharField(max_length=128, db_index=True)
    dateModified = models.DateTimeField(db_index=True)
    creator = models.ForeignKey(User, null=True, blank=True, db_index=True)

    # the canonical serialization of the plan exchanged with javascript clients
    jsonPlan = ExtrasDotField()

    # a place to put an auto-generated summary of the plan
    summary = models.CharField(max_length=4096)

    # allow users to mark plans as deleted.  remember to use this field!
    deleted = models.BooleanField(blank=True, default=False)

    # allow users to mark plans as read only, so when they are opened they cannot be edited
    readOnly = models.BooleanField(blank=True, default=False)

    # cache commonly used stats derived from the plan (relatively expensive to calculate)
    numStations = models.PositiveIntegerField(default=0)
    numSegments = models.PositiveIntegerField(default=0)
    numCommands = models.PositiveIntegerField(default=0)
    lengthMeters = models.FloatField(null=True, blank=True)
    estimatedDurationSeconds = models.FloatField(null=True, blank=True)
    stats = ExtrasDotField(
    )  # a place for richer stats such as numCommandsByType
    namedURLs = GenericRelation(NamedURL)

    class Meta:
        ordering = ['-dateModified']
        abstract = True

    @property
    def acquisition_time(self):
        return self.dateModified

    def get_absolute_url(self):
        return reverse('planner2_plan_save_json', args=[self.pk, self.name])

    def extractFromJson(self,
                        overWriteDateModified=True,
                        overWriteUuid=True,
                        request=None):
        if overWriteUuid:
            if not self.uuid:
                self.uuid = makeUuid()
                self.jsonPlan.uuid = self.uuid
            self.jsonPlan.serverId = self.pk
        if overWriteDateModified:
            self.jsonPlan.dateModified = (datetime.datetime.now(
                pytz.utc).replace(microsecond=0).isoformat())
            self.jsonPlan.dateModified = self.jsonPlan.dateModified[:-6] + 'Z'

        self.name = self.jsonPlan.name
        self.jsonPlan.url = self.get_absolute_url()
        self.jsonPlan.serverId = self.pk
        self.dateModified = dateparser(
            self.jsonPlan.dateModified).replace(tzinfo=pytz.utc)
        plannerUsers = User.objects.filter(username=self.jsonPlan.creator)
        if plannerUsers:
            self.creator = plannerUsers[0]
        else:
            self.creator = None

        # fill in stats
        try:
            exporter = statsPlanExporter.StatsPlanExporter()
            #             print ' about to do stats'
            stats = exporter.exportDbPlan(self, request)
            for f in ('numStations', 'numSegments', 'numCommands',
                      'lengthMeters', 'estimatedDurationSeconds'):
                setattr(self, f, stats[f])
            self.stats.numCommandsByType = stats["numCommandsByType"]
            self.summary = statsPlanExporter.getSummary(stats)
        except:
            logging.warning(
                'extractFromJson: could not extract stats from plan %s',
                self.uuid)
            raise  # FIX
        return self

    def getSummaryOfCommandsByType(self):
        return statsPlanExporter.getSummaryOfCommandsByType(self.stats)

    # TODO test
    def toXpjson(self):
        platform = self.jsonPlan['platform']
        if platform:
            planSchema = getPlanSchema(platform[u'name'])
            return xpjson.loadDocumentFromDict(self.jsonPlan,
                                               schema=planSchema.getSchema())
        logging.warning(
            'toXpjson: could not convert to xpjson, probably no schema %s',
            self.uuid)
        raise  # FIX

    def escapedName(self):
        name = re.sub(r'[^\w]', '', self.name)
        if name == '':
            return 'plan'
        else:
            if self.jsonPlan and self.jsonPlan.planVersion:
                return name + "_" + self.jsonPlan.planVersion
            return name

    def getExportUrl(self, extension):
        return reverse('planner2_planExport',
                       kwargs={
                           'uuid': self.uuid,
                           'name': self.escapedName() + extension
                       })

    def getExporters(self):
        import choosePlanExporter  # delayed import avoids import loop
        result = []
        for exporterInfo in choosePlanExporter.PLAN_EXPORTERS:
            info = copy.deepcopy(exporterInfo)
            info.url = self.getExportUrl(info.extension)
            result.append(info)
        return result

    def getLinks(self):
        """
        The links tab wil be populated with the name, value contents of this dictionary as links,
        name is the string displayed and link is what will be opened
        """
        result = {
            "KML":
            reverse('planner2_planExport',
                    kwargs={
                        'uuid': self.uuid,
                        'name': self.name + '.kml'
                    })
        }
        kwargs = {
            'plan_id': self.pk,
            'crs': settings.XGDS_PLANNER_CRS_UNITS_DEFAULT
        }
        if settings.XGDS_PLANNER_CRS_UNITS_DEFAULT:
            result["SummaryCRS"] = reverse('plan_bearing_distance_crs',
                                           kwargs=kwargs)
        else:
            result["Summary"] = reverse('plan_bearing_distance', kwargs=kwargs)

        for exporter in self.getExporters():
            result[exporter.label] = exporter.url
        return result

    def getEscapedId(self):
        if self.jsonPlan and self.jsonPlan.id:
            result = re.sub(r'[^\w]', '', self.jsonPlan.id)
            result = re.sub('_PLAN$', '', result)
            return result
        else:
            return None

    def toMapDict(self):
        """
        Return a reduced dictionary that will be turned to JSON for rendering in a map
        Here we are just interested in the route plan and not in activities
        We just include stations
        """
        result = {}
        result['id'] = self.uuid
        result['author'] = self.jsonPlan.creator
        result['name'] = self.jsonPlan.name
        result['type'] = 'Plan'
        if self.jsonPlan.notes:
            result['notes'] = self.jsonPlan.notes
        else:
            result['notes'] = ''

        stations = []
        seq = self.jsonPlan.sequence
        for el in seq:
            if el.type == "Station":
                sta = {}
                sta['id'] = el.id
                sta['coords'] = el.geometry.coordinates
                sta['notes'] = ''
                if hasattr(el, 'notes'):
                    if el.notes:
                        sta['notes'] = el.notes
                stations.append(sta)
        result['stations'] = stations
        return result

    def get_tree_json(self):
        result = {
            "title": self.name,
            "key": self.uuid,
            "tooltip": self.jsonPlan.notes,
            "data": {
                "type":
                "PlanLink",  # we cheat so this will be 'live'
                "json":
                reverse('planner2_mapJsonPlan',
                        kwargs={'uuid': str(self.uuid)}),
                "kmlFile":
                reverse('planner2_planExport',
                        kwargs={
                            'uuid': str(self.uuid),
                            'name': self.name + '.kml'
                        }),
                "href":
                reverse('planner2_edit', kwargs={'plan_id': str(self.pk)})
            }
        }
        return result

    @property
    def executions(self):
        return LazyGetModelByName(
            settings.XGDS_PLANNER_PLAN_EXECUTION_MODEL).get().objects.filter(
                plan=self)

    def __unicode__(self):
        if self.name:
            return self.name
        else:
            return 'Unnamed plan ' + self.uuid
Exemplo n.º 16
0
class Overlay(models.Model):
    key = models.AutoField(primary_key=True, unique=True)
    # author: user who owns this overlay in the MapFasten system
    author = models.ForeignKey(User, null=True, blank=True)
    lastModifiedTime = models.DateTimeField()
    name = models.CharField(max_length=50)
    description = models.TextField(blank=True)
    imageSourceUrl = models.URLField(blank=True, verify_exists=False)
    imageData = models.ForeignKey(ImageData,
                                  null=True,
                                  blank=True,
                                  related_name='currentOverlays',
                                  on_delete=models.SET_NULL)
    unalignedQuadTree = models.ForeignKey(QuadTree,
                                          null=True,
                                          blank=True,
                                          related_name='unalignedOverlays',
                                          on_delete=models.SET_NULL)
    alignedQuadTree = models.ForeignKey(QuadTree,
                                        null=True,
                                        blank=True,
                                        related_name='alignedOverlays',
                                        on_delete=models.SET_NULL)
    isPublic = models.BooleanField(
        default=settings.GEOCAM_TIE_POINT_PUBLIC_BY_DEFAULT)
    coverage = models.CharField(
        max_length=255,
        blank=True,
        verbose_name='Name of region covered by the overlay')

    # creator: name of person or organization who should get the credit
    # for producing the overlay
    creator = models.CharField(max_length=255, blank=True)
    sourceDate = models.CharField(max_length=255,
                                  blank=True,
                                  verbose_name='Source image creation date')
    rights = models.CharField(max_length=255,
                              blank=True,
                              verbose_name='Copyright information')
    license = models.URLField(
        verify_exists=False,
        blank=True,
        verbose_name='License permitting reuse (optional)',
        choices=settings.GEOCAM_TIE_POINT_LICENSE_CHOICES)

    # extras: a special JSON-format field that holds additional
    # schema-free fields in the overlay model. Members of the field can
    # be accessed using dot notation. currently used extras subfields
    # include: imageSize, points, transform, bounds
    extras = ExtrasDotField()

    # import/export configuration
    exportFields = ('key', 'lastModifiedTime', 'name', 'description',
                    'imageSourceUrl')
    importFields = ('name', 'description', 'imageSourceUrl')
    importExtrasFields = ('points', 'transform')

    def getAlignedTilesUrl(self):
        if self.isPublic:
            urlName = 'geocamTiePoint_publicTile'
        else:
            urlName = 'geocamTiePoint_tile'
        return reverse(
            urlName,
            args=[str(self.alignedQuadTree.id), '[ZOOM]', '[X]', '[Y].png'])

    def getJsonDict(self):
        # export all schema-free subfields of extras
        result = self.extras.copy()

        # export other schema-controlled fields of self (listed in exportFields)
        for key in self.exportFields:
            val = getattr(self, key, None)
            if val not in ('', None):
                result[key] = val

        # conversions
        result['lastModifiedTime'] = (
            result['lastModifiedTime'].replace(microsecond=0).isoformat() +
            'Z')

        # calculate and export urls for client convenience
        result['url'] = reverse('geocamTiePoint_overlayIdJson',
                                args=[self.key])
        if self.unalignedQuadTree is not None:
            result['unalignedTilesUrl'] = reverse(
                'geocamTiePoint_tile',
                args=[
                    str(self.unalignedQuadTree.id), '[ZOOM]', '[X]', '[Y].jpg'
                ])
            result['unalignedTilesZoomOffset'] = quadTree.ZOOM_OFFSET
        if self.alignedQuadTree is not None:
            result['alignedTilesUrl'] = self.getAlignedTilesUrl()

            # note: when exportZip has not been set, its value is not
            # None but <FieldFile: None>, which is False in bool() context
            if self.alignedQuadTree.exportZip:
                result['exportUrl'] = reverse(
                    'geocamTiePoint_overlayExport',
                    args=[self.key,
                          str(self.alignedQuadTree.exportZipName)])

        return result

    def setJsonDict(self, jsonDict):
        # set schema-controlled fields of self (listed in
        # self.importFields)
        for key in self.importFields:
            val = jsonDict.get(key, MISSING)
            if val is not MISSING:
                setattr(self, key, val)

        # set schema-free subfields of self.extras (listed in
        # self.importExtrasFields)
        for key in self.importExtrasFields:
            val = jsonDict.get(key, MISSING)
            if val is not MISSING:
                self.extras[key] = val

    jsonDict = property(getJsonDict, setJsonDict)

    class Meta:
        ordering = ['-key']

    def __unicode__(self):
        return (
            'Overlay key=%s name=%s author=%s %s' %
            (self.key, self.name, self.author.username, self.lastModifiedTime))

    def save(self, *args, **kwargs):
        self.lastModifiedTime = datetime.datetime.utcnow()
        super(Overlay, self).save(*args, **kwargs)

    def getSlug(self):
        return re.sub('[^\w]', '_', os.path.splitext(self.name)[0])

    def getExportName(self):
        now = datetime.datetime.utcnow()
        return ('mapfasten-%s-%s' %
                (self.getSlug(), now.strftime('%Y-%m-%d-%H%M%S-UTC')))

    def generateUnalignedQuadTree(self):
        qt = QuadTree(imageData=self.imageData)
        qt.save()

        self.unalignedQuadTree = qt
        self.save()

        return qt

    def generateAlignedQuadTree(self):
        if self.extras.get('transform') is None:
            return None

        qt = QuadTree(imageData=self.imageData,
                      transform=dumps(self.extras.transform))
        qt.save()

        self.alignedQuadTree = qt

        return qt

    def generateExport(self):
        (self.alignedQuadTree.generateExport(self.getExportName(),
                                             self.getJsonDict(),
                                             self.getSlug()))
        return self.alignedQuadTree.exportZip

    def updateAlignment(self):
        toPts, fromPts = transform.splitPoints(self.extras.points)
        tform = transform.getTransform(toPts, fromPts)
        self.extras.transform = tform.getJsonDict()

    def getSimpleAlignedOverlayViewer(self, request):
        alignedTilesPath = re.sub(r'/\[ZOOM\].*$', '',
                                  self.getAlignedTilesUrl())
        alignedTilesRootUrl = request.build_absolute_uri(alignedTilesPath)
        return (self.alignedQuadTree.getSimpleViewHtml(alignedTilesRootUrl,
                                                       self.getJsonDict(),
                                                       self.getSlug()))
Exemplo n.º 17
0
class Overlay(models.Model):
    # required fields 
    key = models.AutoField(primary_key=True, unique=True)
    lastModifiedTime = models.DateTimeField()
    name = models.CharField(max_length=50)
    # optional fields
    # author: user who owns this overlay in the system
    author = models.ForeignKey(User, null=True, blank=True)
    description = models.TextField(blank=True)
    imageSourceUrl = models.URLField(blank=True) #, verify_exists=False)
    imageData = models.ForeignKey(ImageData, null=True, blank=True,
                                  related_name='currentOverlays',
                                  on_delete=models.SET_NULL)
    unalignedQuadTree = models.ForeignKey(QuadTree, null=True, blank=True,
                                          related_name='unalignedOverlays',
                                          on_delete=models.SET_NULL)
    alignedQuadTree = models.ForeignKey(QuadTree, null=True, blank=True,
                                        related_name='alignedOverlays',
                                        on_delete=models.SET_NULL)
    isPublic = models.BooleanField(default=settings.GEOCAM_TIE_POINT_PUBLIC_BY_DEFAULT)
    coverage = models.CharField(max_length=255, blank=True,
                                verbose_name='Name of region covered by the overlay')
    # creator: name of person or organization who should get the credit
    # for producing the overlay
    creator = models.CharField(max_length=255, blank=True)
    sourceDate = models.CharField(max_length=255, blank=True,
                                  verbose_name='Source image creation date')
    rights = models.CharField(max_length=255, blank=True,
                              verbose_name='Copyright information')
    license = models.URLField(blank=True,
                              verbose_name='License permitting reuse (optional)',
                              choices=settings.GEOCAM_TIE_POINT_LICENSE_CHOICES)
    centerLat = models.FloatField(null=True, blank=True, default=0)
    centerLon = models.FloatField(null=True, blank=True, default=0) 
    
    nadirLat = models.FloatField(null=True, blank=True, default=0)
    nadirLon = models.FloatField(null=True, blank=True, default=0) 
    
    # extras: a special JSON-format field that holds additional
    # schema-free fields in the overlay model. Members of the field can
    # be accessed using dot notation. currently used extras subfields
    # include: imageSize, points, transform, bounds, centerLat, centerLon, rotatedImageSize
    extras = ExtrasDotField()
    # import/export configuration
    readyToExport = models.BooleanField(default=False)
    # true if output product (geotiff, RMS error, etc) has been written to file. 
    writtenToFile = models.BooleanField(default=False)
    # exportFields: export these fields to the client side (as JSON)
    exportFields = ('key', 'lastModifiedTime', 'name', 'description', 
                    'imageSourceUrl', 'creator', 'readyToExport', 
                    'centerLat', 'centerLon', 'nadirLat', 'nadirLon')
    # importFields: import these fields from the client side and save their values to the database.
    importFields = ('name', 'description', 'imageSourceUrl', 'readyToExport', 
                    'centerLat', 'centerLon', 'nadirLat', 'nadirLon')
    importExtrasFields = ('points', 'transform')

    def getRawImageData(self):
        """
        Returns the original image data created upon image upload (not rotated, not enhanced)
        """
        try:
            imageData = ImageData.objects.filter(overlay__key = self.key).filter(raw = True)
            return imageData[0]
        except:
            # print "Error: no raw image data available"
            return None
        

    def getAlignedTilesUrl(self):
        if self.isPublic:
            urlName = 'geocamTiePoint_publicTile'
        else:
            urlName = 'geocamTiePoint_tile'
        return reverse(urlName,
                       args=[str(self.alignedQuadTree.id)])

    def getJsonDict(self):
        # export all schema-free subfields of extras
        result = self.extras.copy()
        # export other schema-controlled fields of self (listed in exportFields)
        for key in self.exportFields:
            val = getattr(self, key, None)
            if val not in ('', None):
                result[key] = val
        # conversions
        result['lmt_datetime'] = result['lastModifiedTime'].strftime('%F %k:%M')
        result['lastModifiedTime'] = (result['lastModifiedTime']
                                      .replace(microsecond=0)
                                      .isoformat()
                                      + 'Z')
        # calculate and export urls for client convenience
        result['url'] = reverse('geocamTiePoint_overlayIdJson', args=[self.key])
        try: 
            deepzoomRoot = settings.DEEPZOOM_ROOT.replace(settings.PROJ_ROOT, '/')
            deepzoomFile = self.imageData.associated_deepzoom.name + '/' + self.imageData.associated_deepzoom.name + '.dzi'
            result['deepzoom_path'] = deepzoomRoot + deepzoomFile
        except: 
            pass
        # set image size
        result['imageSize'] = [self.imageData.width, self.imageData.height]
        if 'issMRF' not in result:
            result['issMRF'] = self.imageData.issMRF
        if self.unalignedQuadTree is not None:
            result['unalignedTilesUrl'] = reverse('geocamTiePoint_tile',
                                                  args=[str(self.unalignedQuadTree.id)])
            result['unalignedTilesZoomOffset'] = quadTree.ZOOM_OFFSET
        if self.alignedQuadTree is not None:
            result['alignedTilesUrl'] = self.getAlignedTilesUrl()
            # note: when exportZip has not been set, its value is not
            # None but <FieldFile: None>, which is False in bool() context
        # include image enhancement values as part of json. 
        if self.imageData is not None:
            try: 
                result['rotationAngle'] = self.imageData.rotationAngle
                result['brightness'] = self.imageData.brightness
                result['contrast'] = self.imageData.contrast
                result['autoenhance'] = self.imageData.autoenhance
            except:
                pass
        try:
            mission, roll, frame = self.name.split('-')
            result['mission'] = mission
            result['roll'] = roll
            result['frame'] = frame[:-4]
        except:
            pass
        return result

    def setJsonDict(self, jsonDict):
        # set schema-controlled fields of self (listed in
        # self.importFields)
        for key in self.importFields:
            val = jsonDict.get(key, MISSING)
            if val is not MISSING:
                setattr(self, key, val)

        # set schema-free subfields of self.extras (listed in
        # self.importExtrasFields)
        for key in self.importExtrasFields:
            val = jsonDict.get(key, MISSING)
            if val is not MISSING:
                self.extras[key] = val

        # get the image enhancement values and save it to the overlay's imagedata.
        imageDataDict = {}
        imageDataDict['rotationAngle'] = jsonDict.get('rotationAngle', MISSING)
        imageDataDict['contrast'] = jsonDict.get('contrast', MISSING)
        imageDataDict['brightness'] = jsonDict.get('brightness', MISSING)
        imageDataDict['autoenhance'] = jsonDict.get('autoenhance', MISSING)
        for key, value in imageDataDict.items():
            if value is not MISSING:
                try:
                    setattr(self.imageData, key, value)
                except:
                    print "failed to save image data values from the json dict returned from client"
        self.imageData.save()

    jsonDict = property(getJsonDict, setJsonDict)

    class Meta:
        ordering = ['-key']

    def __unicode__(self):
        return ('Overlay key=%s name=%s author=%s %s'
                % (self.key, self.name, self.author.username,
                   self.lastModifiedTime))

    def save(self, *args, **kwargs):
        self.lastModifiedTime = datetime.datetime.utcnow()
        super(Overlay, self).save(*args, **kwargs)

    def getSlug(self):
        return re.sub('[^\w]', '_', os.path.splitext(self.name)[0])

    def getExportName(self):
        now = datetime.datetime.utcnow()
        return 'georef-%s' % self.getSlug()

    def generateUnalignedQuadTree(self):
        qt = QuadTree(imageData=self.imageData)
        qt.save()

        self.unalignedQuadTree = qt
        self.save()

        return qt

    def generateAlignedQuadTree(self):
        if self.extras.get('transform') is None:
            return None
        # grab the original image's imageData
        originalImageData = self.getRawImageData()
        qt = QuadTree(imageData=originalImageData,
                    transform=dumps(self.extras.transform))
        qt.save()
        self.alignedQuadTree = qt
        return qt

    def generateHtmlExport(self):
        (self.alignedQuadTree.generateHtmlExport
         (self.getExportName(),
          self.getJsonDict(),
          self.getSlug()))
        return self.alignedQuadTree.htmlExport 

    def generateKmlExport(self):
        (self.alignedQuadTree.generateKmlExport
         (self.getExportName(),
          self.getJsonDict(),
          self.getSlug()))
        return self.alignedQuadTree.kmlExport 

    def generateGeotiffExport(self):
        (self.alignedQuadTree.generateGeotiffExport
         (self.getExportName(),
          self.getJsonDict(),
          self.getSlug()))
        return self.alignedQuadTree.geotiffExport 
    
    def updateAlignment(self):
        toPts, fromPts = transform.splitPoints(self.extras.points)
        tform = transform.getTransform(toPts, fromPts)
        self.extras.transform = tform.getJsonDict()

    def getSimpleAlignedOverlayViewer(self, request):
        alignedTilesPath = re.sub(r'/\[ZOOM\].*$', '', self.getAlignedTilesUrl())
        alignedTilesRootUrl = request.build_absolute_uri(alignedTilesPath)
        return (self.alignedQuadTree
                .getSimpleViewHtml(alignedTilesRootUrl,
                                   self.getJsonDict(),
                                   self.getSlug()))