Functions/GenXdev.Helpers/Get-ImageGeolocation.cs
// ################################################################################
// Part of PowerShell module : GenXdev.Helpers // Original cmdlet filename : Get-ImageGeolocation.cs // Original author : René Vaessen / GenXdev // Version : 1.3066.2025 // ################################################################################ // Copyright (c) René Vaessen / GenXdev // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ################################################################################ using System; using System.Collections; using System.Drawing; using System.IO; using System.Linq; using System.Management.Automation; namespace GenXdev.Helpers { /// <summary> /// <para type="synopsis"> /// Extracts geolocation data from an image file. /// </para> /// /// <para type="description"> /// This function reads EXIF metadata from an image file to extract its latitude and /// longitude coordinates. It supports images that contain GPS metadata in their EXIF /// data. The function uses the System.Drawing.Image class to load the image and /// parse the GPS coordinates from property items. /// </para> /// /// <para type="description"> /// PARAMETERS /// </para> /// /// <para type="description"> /// -ImagePath <String><br/> /// The full path to the image file to analyze. The file must be a valid image format /// that supports EXIF metadata (JPEG, TIFF, etc.).<br/> /// - <b>Position</b>: 0<br/> /// - <b>Mandatory</b>: true<br/> /// - <b>ValueFromPipeline</b>: true<br/> /// - <b>ValueFromPipelineByPropertyName</b>: true<br/> /// </para> /// /// <example> /// <para>Get geolocation data from an image file</para> /// <para>This example extracts GPS coordinates from a vacation photo.</para> /// <code> /// Get-ImageGeolocation -ImagePath "C:\Pictures\vacation.jpg" /// </code> /// </example> /// /// <example> /// <para>Use pipeline input to get geolocation data</para> /// <para>This example demonstrates using the pipeline to pass the image path.</para> /// <code> /// "C:\Pictures\vacation.jpg" | Get-ImageGeolocation /// </code> /// </example> /// </summary> [Cmdlet(VerbsCommon.Get, "ImageGeolocation")] [OutputType(typeof(Hashtable))] public class GetImageGeolocationCommand : PSGenXdevCmdlet { /// <summary> /// The full path to the image file to analyze /// </summary> [Parameter( Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Path to the image file to analyze")] [ValidateNotNullOrEmpty] public string ImagePath { get; set; } /// <summary> /// Begin processing - validate image path exists /// </summary> protected override void BeginProcessing() { // Use PowerShell Test-Path for exact path resolution behavior var scriptBlock = ScriptBlock.Create("param($path) Test-Path -LiteralPath $path"); var testResult = scriptBlock.Invoke(ImagePath); if (!(bool)testResult[0].BaseObject) { WriteError(new ErrorRecord( new FileNotFoundException($"The specified image path '{ImagePath}' does not exist."), "PathNotFound", ErrorCategory.ObjectNotFound, ImagePath)); return; } WriteVerbose($"Processing image: {ImagePath}"); } /// <summary> /// Process record - extract GPS metadata from image /// </summary> protected override void ProcessRecord() { Image image = null; try { // Load the image file WriteVerbose("Loading image file"); image = Image.FromFile(ImagePath); // Get all property items var propertyItems = image.PropertyItems; // Extract GPS metadata properties WriteVerbose("Extracting GPS metadata"); var latitudeRef = propertyItems.FirstOrDefault(pi => pi.Id == 0x0001); var latitude = propertyItems.FirstOrDefault(pi => pi.Id == 0x0002); var longitudeRef = propertyItems.FirstOrDefault(pi => pi.Id == 0x0003); var longitude = propertyItems.FirstOrDefault(pi => pi.Id == 0x0004); // Check if GPS data exists if (latitude != null && longitude != null && latitudeRef != null && longitudeRef != null) { // Calculate actual latitude and longitude values uint latNum = BitConverter.ToUInt32(latitude.Value, 0); uint latDen = BitConverter.ToUInt32(latitude.Value, 4); double lat = (double)latNum / latDen; uint lonNum = BitConverter.ToUInt32(longitude.Value, 0); uint lonDen = BitConverter.ToUInt32(longitude.Value, 4); double lon = (double)lonNum / lonDen; // Adjust for south and west hemispheres if (latitudeRef.Value[0] == (byte)'S') { lat = -lat; } if (longitudeRef.Value[0] == (byte)'W') { lon = -lon; } WriteVerbose($"GPS coordinates found: {lat}, {lon}"); var result = new Hashtable { ["Latitude"] = lat, ["Longitude"] = lon }; WriteObject(result); } else { WriteVerbose("No GPS metadata found in image"); WriteObject(null); } } catch (Exception ex) { WriteError(new ErrorRecord( ex, "ImageProcessingError", ErrorCategory.InvalidOperation, ImagePath)); } finally { if (image != null) { image.Dispose(); } } } /// <summary> /// End processing - no cleanup needed /// </summary> protected override void EndProcessing() { } } } |