def exportToThreeJS(htmlfilename, context, progress=None):
  mapTo3d = context.mapTo3d
  canvas = context.canvas
  extent = canvas.extent()
  if progress is None:
    progress = dummyProgress
  temp_dir = QDir.tempPath()
  
  #TODO: do in JSWriter?
  timestamp = datetime.datetime.today().strftime("%Y%m%d%H%M%S")
  if htmlfilename == "":
    htmlfilename = tools.temporaryOutputDir() + "/%s.html" % timestamp
  out_dir, filename = os.path.split(htmlfilename)
  if not QDir(out_dir).exists():
    QDir().mkpath(out_dir)

  filetitle = os.path.splitext(filename)[0]

  demProperties = context.properties[ObjectTreeItem.ITEM_DEM]
  isSimpleMode = demProperties.get("radioButton_Simple", False)

  # create JavaScript writer object
  writer = JSWriter(htmlfilename, context)
  writer.openFile(not isSimpleMode)
  writer.writeWorldInfo()
  progress(5)

  #TODO
  writer.timestamp = timestamp

  # write primary DEM
  if isSimpleMode:
    writeSimpleDEM(writer, demProperties, progress)
  else:
    writeMultiResDEM(writer, demProperties, progress)
    writer.prepareNext()

  # write additional DEM(s)
  primaryDEMLayerId = demProperties["comboBox_DEMLayer"]
  for layerId, properties in context.properties[ObjectTreeItem.ITEM_OPTDEM].iteritems():
    if layerId != primaryDEMLayerId and properties.get("visible", False):
      writeSimpleDEM(writer, properties)

  progress(50)

  # write vector data
  writeVectors(writer)
  progress(80)

  # copy three.js files
  tools.copyThreejsFiles(out_dir, context.controls)

  # copy additional library files
  templatePath = os.path.join(tools.templateDir(), context.templateName)
  metadata = tools.getTemplateMetadata(templatePath)
  tools.copyLibraries(out_dir, metadata)

  # generate html file
  with codecs.open(templatePath, "r", "UTF-8") as f:
    html = f.read()

  with codecs.open(htmlfilename, "w", "UTF-8") as f:
    f.write(html.replace("${title}", filetitle).replace("${controls}", '<script src="./threejs/%s"></script>' % context.controls).replace("${options}", writer.options()).replace("${scripts}", writer.scripts()))

  return htmlfilename
Esempio n. 2
0
def runAdvanced(htmlfilename, context, dialog, progress=None):
  mapTo3d = context.mapTo3d
  canvas = context.canvas
  if progress is None:
    progress = dummyProgress
  demlayer = QgsMapLayerRegistry().instance().mapLayer(context.demlayerid)
  temp_dir = QDir.tempPath()
  timestamp = datetime.datetime.today().strftime("%Y%m%d%H%M%S")

  if htmlfilename == "":
    htmlfilename = tools.temporaryOutputDir() + "/%s.html" % timestamp
  out_dir, filename = os.path.split(htmlfilename)
  if not QDir(out_dir).exists():
    QDir().mkpath(out_dir)
  filetitle = os.path.splitext(filename)[0]

  # create quad tree
  quadtree = dialog.createQuadTree()
  if quadtree is None:
    QMessageBox.warning(None, "Qgis2threejs", "Focus point/area is not selected.")
    return
  quads = quadtree.quads()

  # create quads and a point on map canvas with rubber bands
  dialog.createRubberBands(quads, quadtree.focusRect.center())

  # create an image for texture
  image_basesize = 128
  hpw = canvas.extent().height() / canvas.extent().width()
  if hpw < 1:
    image_width = image_basesize
    image_height = round(image_width * hpw)
  else:
    image_height = image_basesize
    image_width = round(image_height * hpw)
  image = QImage(image_width, image_height, QImage.Format_ARGB32_Premultiplied)
  #qDebug("Created image size: %d, %d" % (image_width, image_height))

  layerids = []
  for layer in canvas.layers():
    layerids.append(unicode(layer.id()))

  # set up a renderer
  labeling = QgsPalLabeling()
  renderer = QgsMapRenderer()
  renderer.setOutputSize(image.size(), image.logicalDpiX())
  renderer.setDestinationCrs(context.crs)
  renderer.setProjectionsEnabled(True)
  renderer.setLabelingEngine(labeling)
  renderer.setLayerSet(layerids)

  painter = QPainter()
  antialias = True
  fillColor = canvas.canvasColor()
  if float(".".join(QT_VERSION_STR.split(".")[0:2])) < 4.8:
    fillColor = qRgb(fillColor.red(), fillColor.green(), fillColor.blue())

  # (currently) dem size should be 2 ^ quadtree.height * a + 1, where a is larger integer than 0
  # with smooth resolution change, this is not necessary
  dem_width = dem_height = max(64, 2 ** quadtree.height) + 1

  warp_dem = tools.MemoryWarpRaster(demlayer.source().encode("UTF-8"))
  wkt = str(context.crs.toWkt())

  # create JavaScript writer object
  context.setWarpDem(warp_dem)
  writer = JSWriter(htmlfilename, context)

  unites_center = True
  centerQuads = DEMQuadList(dem_width, dem_height)
  scripts = []
  plane_index = 0
  for i, quad in enumerate(quads):
    progress(50 * i / len(quads))
    extent = quad.extent

    if quad.height < quadtree.height or unites_center == False:
      renderer.setExtent(extent)
      # render map image
      image.fill(fillColor)
      painter.begin(image)
      if antialias:
        painter.setRenderHint(QPainter.Antialiasing)
      renderer.render(painter)
      painter.end()

      if context.localBrowsingMode:
        tex = tools.base64image(image)
      else:
        texfilename = os.path.splitext(htmlfilename)[0] + "_%d.png" % plane_index
        image.save(texfilename)
        tex = os.path.split(texfilename)[1]

    # calculate extent. output dem should be handled as points.
    xres = extent.width() / (dem_width - 1)
    yres = extent.height() / (dem_height - 1)
    geotransform = [extent.xMinimum() - xres / 2, xres, 0, extent.yMaximum() + yres / 2, 0, -yres]

    # warp dem
    dem_values = warp_dem.read(dem_width, dem_height, wkt, geotransform)
    if mapTo3d.multiplierZ != 1:
      dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)
    if debug_mode:
      qDebug("Warped DEM: %d x %d, extent %s" % (dem_width, dem_height, str(geotransform)))

    # generate javascript data file
    planeWidth = mapTo3d.planeWidth * extent.width() / canvas.extent().width()
    planeHeight = mapTo3d.planeHeight * extent.height() / canvas.extent().height()
    offsetX = mapTo3d.planeWidth * (extent.xMinimum() - canvas.extent().xMinimum()) / canvas.extent().width() + planeWidth / 2 - mapTo3d.planeWidth / 2
    offsetY = mapTo3d.planeHeight * (extent.yMinimum() - canvas.extent().yMinimum()) / canvas.extent().height() + planeHeight / 2 - mapTo3d.planeHeight / 2

    # value resampling on edges for combination with different resolution DEM
    neighbors = quadtree.neighbors(quad)
    #qDebug("Output quad (%d %s): height=%d" % (i, str(quad), quad.height))
    for direction, neighbor in enumerate(neighbors):
      if neighbor is None:
        continue
      #qDebug(" neighbor %d %s: height=%d" % (direction, str(neighbor), neighbor.height))
      interval = 2 ** (quad.height - neighbor.height)
      if interval > 1:
        if direction == QuadTree.UP or direction == QuadTree.DOWN:
          y = 0 if direction == QuadTree.UP else dem_height - 1
          for x1 in range(interval, dem_width, interval):
            x0 = x1 - interval
            z0 = dem_values[x0 + dem_width * y]
            z1 = dem_values[x1 + dem_width * y]
            for xx in range(1, interval):
              z = (z0 * (interval - xx) + z1 * xx) / interval
              dem_values[x0 + xx + dem_width * y] = z
        else:   # LEFT or RIGHT
          x = 0 if direction == QuadTree.LEFT else dem_width - 1
          for y1 in range(interval, dem_height, interval):
            y0 = y1 - interval
            z0 = dem_values[x + dem_width * y0]
            z1 = dem_values[x + dem_width * y1]
            for yy in range(1, interval):
              z = (z0 * (interval - yy) + z1 * yy) / interval
              dem_values[x + dem_width * (y0 + yy)] = z

    if quad.height < quadtree.height or unites_center == False:
      writer.openFile(True)
      opt = "{width:%f,height:%f,offsetX:%f,offsetY:%f}" % (planeWidth, planeHeight, offsetX, offsetY)
      writer.write('dem[%d] = {width:%d,height:%d,plane:%s,data:[%s]};\n' % (plane_index, dem_width, dem_height, opt, ",".join(map(gdal2threejs.formatValue, dem_values))))
      writer.write('tex[%d] = "%s";\n' % (plane_index, tex))
      plane_index += 1
    else:
      centerQuads.addQuad(quad, dem_values)

  if unites_center:
    extent = centerQuads.extent()
    if hpw < 1:
      image_width = image_basesize * centerQuads.width()
      image_height = round(image_width * hpw)
    else:
      image_height = image_basesize * centerQuads.height()
      image_width = round(image_height * hpw)
    image = QImage(image_width, image_height, QImage.Format_ARGB32_Premultiplied)
    #qDebug("Created image size: %d, %d" % (image_width, image_height))

    renderer.setOutputSize(image.size(), image.logicalDpiX())
    renderer.setExtent(extent)
    # render map image
    image.fill(fillColor)
    painter.begin(image)
    if antialias:
      painter.setRenderHint(QPainter.Antialiasing)
    renderer.render(painter)
    painter.end()

    if context.localBrowsingMode:
      tex = tools.base64image(image)
    else:
      texfilename = os.path.splitext(htmlfilename)[0] + "_%d.png" % plane_index
      image.save(texfilename)
      tex = os.path.split(texfilename)[1]

    dem_values = centerQuads.unitedDEM()
    planeWidth = mapTo3d.planeWidth * extent.width() / canvas.extent().width()
    planeHeight = mapTo3d.planeHeight * extent.height() / canvas.extent().height()
    offsetX = mapTo3d.planeWidth * (extent.xMinimum() - canvas.extent().xMinimum()) / canvas.extent().width() + planeWidth / 2 - mapTo3d.planeWidth / 2
    offsetY = mapTo3d.planeHeight * (extent.yMinimum() - canvas.extent().yMinimum()) / canvas.extent().height() + planeHeight / 2 - mapTo3d.planeHeight / 2

    dem_width = (dem_width - 1) * centerQuads.width() + 1
    dem_height = (dem_height - 1) * centerQuads.height() + 1

    writer.openFile(True)
    opt = "{width:%f,height:%f,offsetX:%f,offsetY:%f}" % (planeWidth, planeHeight, offsetX, offsetY)
    writer.write('dem[%d] = {width:%d,height:%d,plane:%s,data:[%s]};\n' % (plane_index, dem_width, dem_height, opt, ",".join(map(gdal2threejs.formatValue, dem_values))))
    writer.write('tex[%d] = "%s";\n' % (plane_index, tex))
    plane_index += 1
  progress(50)

  # vector data output
  writer.prepareNext()
  writeVectors(writer)
  progress(80)

  # copy files from template
  tools.copyThreejsFiles(out_dir)

  # generate html file
  with codecs.open(tools.pluginDir() + "/template.html", "r", "UTF-8") as f:
    html = f.read()

  with codecs.open(htmlfilename, "w", "UTF-8") as f:
    f.write(html.replace("${title}", filetitle).replace("${scripts}", writer.scripts()))

  return htmlfilename
def runAdvanced(htmlfilename, context, dialog, progress=None):
    mapTo3d = context.mapTo3d
    canvas = context.canvas
    if progress is None:
        progress = dummyProgress
    demlayer = QgsMapLayerRegistry().instance().mapLayer(context.demlayerid)
    temp_dir = QDir.tempPath()
    timestamp = datetime.datetime.today().strftime("%Y%m%d%H%M%S")

    if htmlfilename == "":
        htmlfilename = tools.temporaryOutputDir() + "/%s.html" % timestamp
    out_dir, filename = os.path.split(htmlfilename)
    if not QDir(out_dir).exists():
        QDir().mkpath(out_dir)
    filetitle = os.path.splitext(filename)[0]

    # create quad tree
    quadtree = dialog.createQuadTree()
    if quadtree is None:
        QMessageBox.warning(None, "Qgis2threejs",
                            "Focus point/area is not selected.")
        return
    quads = quadtree.quads()

    # create quads and a point on map canvas with rubber bands
    dialog.createRubberBands(quads, quadtree.focusRect.center())

    # create an image for texture
    image_basesize = 128
    hpw = canvas.extent().height() / canvas.extent().width()
    if hpw < 1:
        image_width = image_basesize
        image_height = round(image_width * hpw)
    else:
        image_height = image_basesize
        image_width = round(image_height * hpw)
    image = QImage(image_width, image_height,
                   QImage.Format_ARGB32_Premultiplied)
    #qDebug("Created image size: %d, %d" % (image_width, image_height))

    layerids = []
    for layer in canvas.layers():
        layerids.append(unicode(layer.id()))

    # set up a renderer
    labeling = QgsPalLabeling()
    renderer = QgsMapRenderer()
    renderer.setOutputSize(image.size(), image.logicalDpiX())
    renderer.setDestinationCrs(context.crs)
    renderer.setProjectionsEnabled(True)
    renderer.setLabelingEngine(labeling)
    renderer.setLayerSet(layerids)

    painter = QPainter()
    antialias = True
    fillColor = canvas.canvasColor()
    if float(".".join(QT_VERSION_STR.split(".")[0:2])) < 4.8:
        fillColor = qRgb(fillColor.red(), fillColor.green(), fillColor.blue())

    # (currently) dem size should be 2 ^ quadtree.height * a + 1, where a is larger integer than 0
    # with smooth resolution change, this is not necessary
    dem_width = dem_height = max(64, 2**quadtree.height) + 1

    warp_dem = tools.MemoryWarpRaster(demlayer.source().encode("UTF-8"))
    wkt = str(context.crs.toWkt())

    # create JavaScript writer object
    context.setWarpDem(warp_dem)
    writer = JSWriter(htmlfilename, context)

    unites_center = True
    centerQuads = DEMQuadList(dem_width, dem_height)
    scripts = []
    plane_index = 0
    for i, quad in enumerate(quads):
        progress(50 * i / len(quads))
        extent = quad.extent

        if quad.height < quadtree.height or unites_center == False:
            renderer.setExtent(extent)
            # render map image
            image.fill(fillColor)
            painter.begin(image)
            if antialias:
                painter.setRenderHint(QPainter.Antialiasing)
            renderer.render(painter)
            painter.end()

            if context.localBrowsingMode:
                tex = tools.base64image(image)
            else:
                texfilename = os.path.splitext(
                    htmlfilename)[0] + "_%d.png" % plane_index
                image.save(texfilename)
                tex = os.path.split(texfilename)[1]

        # calculate extent. output dem should be handled as points.
        xres = extent.width() / (dem_width - 1)
        yres = extent.height() / (dem_height - 1)
        geotransform = [
            extent.xMinimum() - xres / 2, xres, 0,
            extent.yMaximum() + yres / 2, 0, -yres
        ]

        # warp dem
        dem_values = warp_dem.read(dem_width, dem_height, wkt, geotransform)
        if mapTo3d.multiplierZ != 1:
            dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)
        if debug_mode:
            qDebug("Warped DEM: %d x %d, extent %s" %
                   (dem_width, dem_height, str(geotransform)))

        # generate javascript data file
        planeWidth = mapTo3d.planeWidth * extent.width() / canvas.extent(
        ).width()
        planeHeight = mapTo3d.planeHeight * extent.height() / canvas.extent(
        ).height()
        offsetX = mapTo3d.planeWidth * (
            extent.xMinimum() - canvas.extent().xMinimum()
        ) / canvas.extent().width() + planeWidth / 2 - mapTo3d.planeWidth / 2
        offsetY = mapTo3d.planeHeight * (
            extent.yMinimum() - canvas.extent().yMinimum()) / canvas.extent(
            ).height() + planeHeight / 2 - mapTo3d.planeHeight / 2

        # value resampling on edges for combination with different resolution DEM
        neighbors = quadtree.neighbors(quad)
        #qDebug("Output quad (%d %s): height=%d" % (i, str(quad), quad.height))
        for direction, neighbor in enumerate(neighbors):
            if neighbor is None:
                continue
            #qDebug(" neighbor %d %s: height=%d" % (direction, str(neighbor), neighbor.height))
            interval = 2**(quad.height - neighbor.height)
            if interval > 1:
                if direction == QuadTree.UP or direction == QuadTree.DOWN:
                    y = 0 if direction == QuadTree.UP else dem_height - 1
                    for x1 in range(interval, dem_width, interval):
                        x0 = x1 - interval
                        z0 = dem_values[x0 + dem_width * y]
                        z1 = dem_values[x1 + dem_width * y]
                        for xx in range(1, interval):
                            z = (z0 * (interval - xx) + z1 * xx) / interval
                            dem_values[x0 + xx + dem_width * y] = z
                else:  # LEFT or RIGHT
                    x = 0 if direction == QuadTree.LEFT else dem_width - 1
                    for y1 in range(interval, dem_height, interval):
                        y0 = y1 - interval
                        z0 = dem_values[x + dem_width * y0]
                        z1 = dem_values[x + dem_width * y1]
                        for yy in range(1, interval):
                            z = (z0 * (interval - yy) + z1 * yy) / interval
                            dem_values[x + dem_width * (y0 + yy)] = z

        if quad.height < quadtree.height or unites_center == False:
            writer.openFile(True)
            opt = "{width:%f,height:%f,offsetX:%f,offsetY:%f}" % (
                planeWidth, planeHeight, offsetX, offsetY)
            writer.write(
                'dem[%d] = {width:%d,height:%d,plane:%s,data:[%s]};\n' %
                (plane_index, dem_width, dem_height, opt, ",".join(
                    map(gdal2threejs.formatValue, dem_values))))
            writer.write('tex[%d] = "%s";\n' % (plane_index, tex))
            plane_index += 1
        else:
            centerQuads.addQuad(quad, dem_values)

    if unites_center:
        extent = centerQuads.extent()
        if hpw < 1:
            image_width = image_basesize * centerQuads.width()
            image_height = round(image_width * hpw)
        else:
            image_height = image_basesize * centerQuads.height()
            image_width = round(image_height * hpw)
        image = QImage(image_width, image_height,
                       QImage.Format_ARGB32_Premultiplied)
        #qDebug("Created image size: %d, %d" % (image_width, image_height))

        renderer.setOutputSize(image.size(), image.logicalDpiX())
        renderer.setExtent(extent)
        # render map image
        image.fill(fillColor)
        painter.begin(image)
        if antialias:
            painter.setRenderHint(QPainter.Antialiasing)
        renderer.render(painter)
        painter.end()

        if context.localBrowsingMode:
            tex = tools.base64image(image)
        else:
            texfilename = os.path.splitext(
                htmlfilename)[0] + "_%d.png" % plane_index
            image.save(texfilename)
            tex = os.path.split(texfilename)[1]

        dem_values = centerQuads.unitedDEM()
        planeWidth = mapTo3d.planeWidth * extent.width() / canvas.extent(
        ).width()
        planeHeight = mapTo3d.planeHeight * extent.height() / canvas.extent(
        ).height()
        offsetX = mapTo3d.planeWidth * (
            extent.xMinimum() - canvas.extent().xMinimum()
        ) / canvas.extent().width() + planeWidth / 2 - mapTo3d.planeWidth / 2
        offsetY = mapTo3d.planeHeight * (
            extent.yMinimum() - canvas.extent().yMinimum()) / canvas.extent(
            ).height() + planeHeight / 2 - mapTo3d.planeHeight / 2

        dem_width = (dem_width - 1) * centerQuads.width() + 1
        dem_height = (dem_height - 1) * centerQuads.height() + 1

        writer.openFile(True)
        opt = "{width:%f,height:%f,offsetX:%f,offsetY:%f}" % (
            planeWidth, planeHeight, offsetX, offsetY)
        writer.write('dem[%d] = {width:%d,height:%d,plane:%s,data:[%s]};\n' %
                     (plane_index, dem_width, dem_height, opt, ",".join(
                         map(gdal2threejs.formatValue, dem_values))))
        writer.write('tex[%d] = "%s";\n' % (plane_index, tex))
        plane_index += 1
    progress(50)

    # vector data output
    writer.prepareNext()
    writeVectors(writer)
    progress(80)

    # copy files from template
    tools.copyThreejsFiles(out_dir)

    # generate html file
    with codecs.open(tools.pluginDir() + "/template.html", "r", "UTF-8") as f:
        html = f.read()

    with codecs.open(htmlfilename, "w", "UTF-8") as f:
        f.write(
            html.replace("${title}",
                         filetitle).replace("${scripts}", writer.scripts()))

    return htmlfilename
Esempio n. 4
0
def runSimple(htmlfilename, context, progress=None):
  mapTo3d = context.mapTo3d
  canvas = context.canvas
  extent = canvas.extent()
  demlayer = QgsMapLayerRegistry().instance().mapLayer(context.demlayerid)
  if progress is None:
    progress = dummyProgress
  temp_dir = QDir.tempPath()
  timestamp = datetime.datetime.today().strftime("%Y%m%d%H%M%S")

  if htmlfilename == "":
    htmlfilename = tools.temporaryOutputDir() + "/%s.html" % timestamp
  out_dir, filename = os.path.split(htmlfilename)
  if not QDir(out_dir).exists():
    QDir().mkpath(out_dir)

  filetitle = os.path.splitext(filename)[0]

  # save map canvas image
  if context.localBrowsingMode:
    texfilename = os.path.join(temp_dir, "tex%s.png" % (timestamp))
    canvas.saveAsImage(texfilename)
    tex = gdal2threejs.base64image(texfilename)
    tools.removeTemporaryFiles([texfilename, texfilename + "w"])
  else:
    texfilename = os.path.splitext(htmlfilename)[0] + ".png"
    canvas.saveAsImage(texfilename)
    tex = os.path.split(texfilename)[1]
    tools.removeTemporaryFiles([texfilename + "w"])
  progress(20)

  # warp dem
  # calculate extent. output dem should be handled as points.
  xres = extent.width() / (context.dem_width - 1)
  yres = extent.height() / (context.dem_height - 1)
  geotransform = [extent.xMinimum() - xres / 2, xres, 0, extent.yMaximum() + yres / 2, 0, -yres]
  wkt = str(context.crs.toWkt())

  warp_dem = tools.MemoryWarpRaster(demlayer.source().encode("UTF-8"))
  dem_values = warp_dem.read(context.dem_width, context.dem_height, wkt, geotransform)
  if mapTo3d.multiplierZ != 1:
    dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)
  if debug_mode:
    qDebug("Warped DEM: %d x %d, extent %s" % (context.dem_width, context.dem_height, str(geotransform)))

  # create JavaScript writer object
  context.setWarpDem(warp_dem)
  writer = JSWriter(htmlfilename, context)
  writer.openFile()

  # write dem data
  offsetX = offsetY = 0
  opt = "{width:%f,height:%f,offsetX:%f,offsetY:%f}" % (mapTo3d.planeWidth, mapTo3d.planeHeight, offsetX, offsetY)
  writer.write('dem[0] = {width:%d,height:%d,plane:%s,data:[%s]};\n' % (context.dem_width, context.dem_height, opt, ",".join(map(gdal2threejs.formatValue, dem_values))))
  writer.write('tex[0] = "%s";\n' % tex)
  progress(50)

  # write vector data
  writeVectors(writer)
  progress(80)

  # copy files from template
  tools.copyThreejsFiles(out_dir)

  # generate html file
  with codecs.open(tools.pluginDir() + "/template.html", "r", "UTF-8") as f:
    html = f.read()

  with codecs.open(htmlfilename, "w", "UTF-8") as f:
    f.write(html.replace("${title}", filetitle).replace("${scripts}", writer.scripts()))

  return htmlfilename
def runSimple(htmlfilename, context, progress=None):
    mapTo3d = context.mapTo3d
    canvas = context.canvas
    extent = canvas.extent()
    demlayer = QgsMapLayerRegistry().instance().mapLayer(context.demlayerid)
    if progress is None:
        progress = dummyProgress
    temp_dir = QDir.tempPath()
    timestamp = datetime.datetime.today().strftime("%Y%m%d%H%M%S")

    if htmlfilename == "":
        htmlfilename = tools.temporaryOutputDir() + "/%s.html" % timestamp
    out_dir, filename = os.path.split(htmlfilename)
    if not QDir(out_dir).exists():
        QDir().mkpath(out_dir)

    filetitle = os.path.splitext(filename)[0]

    # save map canvas image
    if context.localBrowsingMode:
        texfilename = os.path.join(temp_dir, "tex%s.png" % (timestamp))
        canvas.saveAsImage(texfilename)
        tex = gdal2threejs.base64image(texfilename)
        tools.removeTemporaryFiles([texfilename, texfilename + "w"])
    else:
        texfilename = os.path.splitext(htmlfilename)[0] + ".png"
        canvas.saveAsImage(texfilename)
        tex = os.path.split(texfilename)[1]
        tools.removeTemporaryFiles([texfilename + "w"])
    progress(20)

    # warp dem
    # calculate extent. output dem should be handled as points.
    xres = extent.width() / (context.dem_width - 1)
    yres = extent.height() / (context.dem_height - 1)
    geotransform = [
        extent.xMinimum() - xres / 2, xres, 0,
        extent.yMaximum() + yres / 2, 0, -yres
    ]
    wkt = str(context.crs.toWkt())

    warp_dem = tools.MemoryWarpRaster(demlayer.source().encode("UTF-8"))
    dem_values = warp_dem.read(context.dem_width, context.dem_height, wkt,
                               geotransform)
    if mapTo3d.multiplierZ != 1:
        dem_values = map(lambda x: x * mapTo3d.multiplierZ, dem_values)
    if debug_mode:
        qDebug("Warped DEM: %d x %d, extent %s" %
               (context.dem_width, context.dem_height, str(geotransform)))

    # create JavaScript writer object
    context.setWarpDem(warp_dem)
    writer = JSWriter(htmlfilename, context)
    writer.openFile()

    # write dem data
    offsetX = offsetY = 0
    opt = "{width:%f,height:%f,offsetX:%f,offsetY:%f}" % (
        mapTo3d.planeWidth, mapTo3d.planeHeight, offsetX, offsetY)
    writer.write('dem[0] = {width:%d,height:%d,plane:%s,data:[%s]};\n' %
                 (context.dem_width, context.dem_height, opt, ",".join(
                     map(gdal2threejs.formatValue, dem_values))))
    writer.write('tex[0] = "%s";\n' % tex)
    progress(50)

    # write vector data
    writeVectors(writer)
    progress(80)

    # copy files from template
    tools.copyThreejsFiles(out_dir)

    # generate html file
    with codecs.open(tools.pluginDir() + "/template.html", "r", "UTF-8") as f:
        html = f.read()

    with codecs.open(htmlfilename, "w", "UTF-8") as f:
        f.write(
            html.replace("${title}",
                         filetitle).replace("${scripts}", writer.scripts()))

    return htmlfilename