Skip to content

Instantly share code, notes, and snippets.

@timlinux
Last active December 26, 2023 02:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timlinux/1057c2c6b5ebda6ed2b5aa8c5aa21372 to your computer and use it in GitHub Desktop.
Save timlinux/1057c2c6b5ebda6ed2b5aa8c5aa21372 to your computer and use it in GitHub Desktop.
A simple attempt to make a spinning globe in QGIS by continuously manipulating the CRS.

QGIS Globe Spinner

A simple attempt to make a spinning globe in QGIS by continuously manipulating the CRS. Just load a world boundaries layer and run this in the QGIS console. Definitely very prototype stuff here so be warned...

temporary

The script currently uses Orthographic North Projection.

For a great resource on projections suited for azimuthal maps, visit progonos.com.

Also read this great analysis of why rendering artifacts are produced.

Here is also a nice example of using R to do the same thing whilst avoiding artifacts.

Note 1: To generate the gif you need to have imagemagick installed. OSX users can easily get it using brew. Linux users using apt/dnf/package manager of your choice. Windows users sorry you will have to figure that one out yourself.

Note 2: Running this script is going to create a lot of user defined CRS's in your User CRS database in QGIS...

# This will make the QGIS use a world projection and then move the center
# of the CRS sequentially to create a spinning globe effect
import os
from PyQt5.QtGui import QImage, QPainter
from PyQt5.QtCore import QEasingCurve
os.system('rm /tmp/globe*')
# Make this a big number while testing e.g. 19
# small number e.g. 1 will make for a smooth animation
longitude_increments = -1
image_counter = 1
# Keep the scales the same if you dont want it to zoom in an out
max_scale = 58589836
min_scale = 1830932
def render_image(name):
size = iface.mapCanvas().size()
image = QImage(size, QImage.Format_RGB32)
painter = QPainter(image)
settings = iface.mapCanvas().mapSettings()
# You can fine tune the settings here for different
# dpi, extent, antialiasing...
# Just make sure the size of the target image matches
job = QgsMapRendererCustomPainterJob(settings, painter)
job.renderSynchronously()
painter.end()
image.save(name)
# See https://doc.qt.io/qt-5/qeasingcurve.html#Type-enum
# For the full list of available easings
map_easing = QEasingCurve(QEasingCurve.InBounce)
zoom_easing = QEasingCurve(QEasingCurve.InQuad)
for i in range(360, 0, longitude_increments):
longitude = i - 180
# Get latitude_factor as but scaled between 0 - 180
latitude_factor = i / 2
# Scale latitude_factor to a positive number from 0 to 1 for use in the easing function
# we invert the result so the flight starts at the equator rather than the poles
latitude_easing_factor = 1 - (latitude_factor / 180)
# Multiply j by the easing of y_value to create a nice wobbly effect
# as the earth rotates
latitude = (latitude_factor * map_easing.valueForProgress(latitude_easing_factor) - 90)
definition = (
'+proj=ortho +lat_0=%f +lon_0=%f +x_0=0 +y_0=0 +ellps=sphere +units=m +no_defs' % (latitude, longitude))
crs = QgsCoordinateReferenceSystem()
crs.createFromProj(definition)
iface.mapCanvas().setDestinationCrs(crs)
# Now use easings for zoom level too
zoom_easing_factor = zoom_easing.valueForProgress(latitude_easing_factor)
scale = ((max_scale - min_scale) * zoom_easing_factor) + min_scale
print('Longitude: %f Latitude: %f Latitude Easing Factor: %f Zoom Easing Factor %f Zoom Scale: %f' %
(longitude, latitude, latitude_easing_factor, zoom_easing_factor, scale))
if zoom_easing_factor == 1:
iface.mapCanvas().zoomToFullExtent()
else:
iface.mapCanvas().zoomScale(scale)
name = ('/tmp/globe-%03d.png' % image_counter)
render_image(name)
image_counter += 1
# Now generate the GIF. If this fails try run the call from the command line
# and check the path to convert (provided by ImageMagick) is correct...
# delay of 3.33 makes the output around 30fps
os.system('/usr/bin/convert -delay 3.33 -loop 0 /tmp/globe-*.png /tmp/globe.gif')
# Now do a second pass with image magick to resize and compress the gif as much as possible.
# The remap option basically takes the first image as a reference inmage for the colour palette
# Depending on you cartography you may also want to bump up the colors param to increase palette size
# and of course adjust the scale factor to the ultimate image size you want
os.system('/usr/bin/convert /tmp/globe.gif -coalesce -scale 600x600 -fuzz 2% +dither -remap /tmp/globe.gif[20] +dither -colors 14 -layers Optimize /tmp/globe_small.gif')
# Also we will make a video of the scene - useful for cases where you have a larger colour
# pallette and gif will not hack it
# Pad option is to deal with cases where ffmpeg complains because the h or w of the image
# is an odd number of pixels.
# :color=white pads the video with white pixels. Change to black if needed.
#os.system('ffmpeg -framerate 30 -pattern_type glob -i "/tmp/globe-*.png" -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2:color=white" -c:v libx264 -pix_fmt yuv420p /tmp/globe.mp4')
@frostbrosracing
Copy link

Your globe is spinning backward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment