diff --git a/Environment/HomogeneousMedium.h b/Environment/HomogeneousMedium.h
new file mode 100644
index 0000000000000000000000000000000000000000..b1a465959741daedc3a2d27c9cf44986be47da2c
--- /dev/null
+++ b/Environment/HomogeneousMedium.h
@@ -0,0 +1,29 @@
+#ifndef _include_HomogeneousMedium_h_
+#define _include_HomogeneousMedium_h_
+
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/units/PhysicalUnits.h>
+
+/**
+ * a homogeneous medium
+ */
+
+namespace corsika::environment {
+
+  template <class T>
+  class HomogeneousMedium : T {
+    MassDensityType const fDensity;
+    NuclearComposition const fNuclComp;
+
+  public:
+    HomogeneousMedium(MassDensityType pDensity, NuclearComposition pNuclComp)
+        : fDensity(pDensity)
+        , fNuclComp(pNuclComp){};
+
+    MassDensityType GetMassDensity(Point const& p) const override { return fDensity; }
+    NuclearComposition const& GetNuclearComposition() const override { return fNuclComp; }
+  };
+
+} // namespace corsika::environment
+#endif
diff --git a/Environment/HydrogenSphere/HydrogenSphere.h b/Environment/HydrogenSphere/HydrogenSphere.h
deleted file mode 100644
index ca5b0f31439f0d35c82bc21e64b283f866df3758..0000000000000000000000000000000000000000
--- a/Environment/HydrogenSphere/HydrogenSphere.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef _include_HydrogenSphere_h_
-#define _include_HydrogenSphere_h_
-
-#include <corsika/geometry/CoordinateSystem.h>
-#include <corsika/particles/ParticleProperties.h>
-#include <corsika/units/PhysicalUnits.h>
-
-/**
- * a fSphere homogeneously filled with hydrogen
- */
-
-namespace corsika::environment {
-
-class HydrogenSphere {
-  CoordinateSystem const& fCS;
-  corsika::geometry::Sphere const fSphere;
-  MassDensityType const fDensity;
-
-public:
-  HydrogenSphere(corsika::geometry::CoordinateSystem const& pEnvCS, LengthType pRadius,
-                 MassDensityType pDensity)
-      : fCS(pEnvCS) fSphere(corsika::geometry::Point(pEnvCS, {0_m, 0_m, 0_m}), radius)
-      , fDensity(pDensity) {}
-
-  auto GetTargetParticle(Point const& p) const {
-    return fSphere.isInside(p) ? corsika::particles::Code::Proton
-                               : corsika::particles::Code::Unknown;
-  }
-
-  MassDensityType GetDensity(Point const& p) const { return fSphere.isInside(p) ? density : 0_kg / (meter*meter*meter); };
-
-  GetMagneticField(Point const& p) {
-    QuantityVector<magnetic_flux_density_d> components{0 * corsika::units::tesla,
-                                                       0. * corsika::units::tesla,
-                                                       0. * corsika::units::tesla};
-    return Vector<magnetic_flux_density_d>(fCS, components);
-  }
-};
-
-}
-#endif
diff --git a/Environment/IMediumModel.h b/Environment/IMediumModel.h
new file mode 100644
index 0000000000000000000000000000000000000000..f291246cb6da9ffe4fa139c72cc1af61a48fe40a
--- /dev/null
+++ b/Environment/IMediumModel.h
@@ -0,0 +1,25 @@
+#ifndef _include_IMediumModel_h
+#define _include_IMediumModels_h
+
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/geometry/BaseTrajectory.h>
+#include <corsika/units/PhysicalUnits.h>
+#include <tuple>
+#include <vector>
+
+namespace corsika::environment {
+
+  class IMediumModel {
+  public:
+    virtual ~IMediumModel() = default;
+
+    virtual corsika::units::si::MassDensityType GetMassDensity(
+        corsika::geometry::Point const&) const = 0;
+    virtual corsika::units::si::GrammageType IntegratedGrammage(BaseTrajectory const&,
+                                                                double, double) const = 0;
+    virtual NuclearComposition const& GetNuclearComposition() const = 0;
+  };
+
+} // namespace corsika::environment
+
+#endif
diff --git a/Environment/NuclearComposition.h b/Environment/NuclearComposition.h
new file mode 100644
index 0000000000000000000000000000000000000000..0fda0ee44ee31acebc8d8f760a2d4a48ba8750b4
--- /dev/null
+++ b/Environment/NuclearComposition.h
@@ -0,0 +1,34 @@
+#ifndef _include_NuclearComposition_h
+#define _include_NuclearComposition_h
+
+#include <algorithm>
+#include <vector>
+
+namespace corsika::environment {
+  class NuclearComposition {
+    std::vector<float> const fNumberFractions; //<! relative fractions of number density
+    std::vector<corsika::particles::Code> const
+        fComponents; //!< particle codes of consitutents
+
+  public:
+    NuclearComposition(std::vector<corsika::particles::Code> pComponents,
+                       std::vector<float> pFractions)
+        : fNumberFractions(pFractions)
+        , fComponents(pComponents) {
+      auto const sumFractions =
+          std::accumulate(pFractions.cbegin(), pFractions.cend(), 0.f);
+
+      if (!(0.999f < sum && sum < 1.001f)) {
+        throw std::string("element fractions do not add up to 1");
+      }
+    }
+
+    auto size() const { return fNumberFractions.size(); }
+
+    auto const& GetFractions() const { return fNumberFractions; }
+    auto const& GetComponents() const { return fComponents; }
+  };
+
+} // namespace corsika::environment
+
+#endif
diff --git a/Environment/VolumeTreeNode.h b/Environment/VolumeTreeNode.h
index 2dcc0caf60c8456229ed29aafd02d633680a7037..7026ff06e33d5cfff661b440731bae70387da543 100644
--- a/Environment/VolumeTreeNode.h
+++ b/Environment/VolumeTreeNode.h
@@ -1,113 +1,114 @@
 #ifndef _include_VolumeTreeNode_H
 #define _include_VolumeTreeNode_H
 
+#include <corsika/geometry/Volume.h>
+#include <memory>
+#include <vector>
+
 namespace corsika::environment {
 
-class Empty {}; //<! intended for usage as default template arguments
+  class Empty {}; //<! intended for usage as default template argument
 
-template <typename IModelProperties=Empty>
-class VolumeTreeNode {
-public:
+  template <typename IModelProperties = Empty>
+  class VolumeTreeNode {
+  public:
     using VTNUPtr = std::unique_ptr<VolumeTreeNode<IModelProperties>>;
     using IMPSharedPtr = std::shared_ptr<IModelProperties>;
-    
-    VolumeTreeNode(VolUPtr pVolume = nullptr) : fGeoVolume(std::move(pVolume)) { }
-
-    bool Contains(Point const& p) const { return fGeoVolume->Contains(p); }
-    
-    VolumeTreeNode<IModelProperties> const* Excludes(Point const& p) const {
-        auto exclContainsIter = std::find_if(fExcludedNodes.cbegin(), fExcludedNodes.cend(),
-            [&] (auto const& s) {return bool(s->Contains(p));});
-        
-        return exclContainsIter != fExcludedNodes.cend() ? *exclContainsIter : nullptr;
+    using VolUPtr = std::unique_ptr<corsika::geometry::Volume>;
+
+    VolumeTreeNode(VolUPtr pVolume = nullptr)
+        : fGeoVolume(std::move(pVolume)) {}
+
+    bool Contains(corsika::geometry::Point const& p) const {
+      return fGeoVolume->Contains(p);
+    }
+
+    VolumeTreeNode<IModelProperties> const* Excludes(
+        corsika::geometry::Point const& p) const {
+      auto exclContainsIter =
+          std::find_if(fExcludedNodes.cbegin(), fExcludedNodes.cend(),
+                       [&](auto const& s) { return bool(s->Contains(p)); });
+
+      return exclContainsIter != fExcludedNodes.cend() ? *exclContainsIter : nullptr;
     }
 
     /** returns a pointer to the sub-VolumeTreeNode which is "responsible" for the given
      * \class Point \arg p, or nullptr iff \arg p is not contained in this volume.
      */
-    VolumeTreeNode<IModelProperties> const* GetContainingNode(Point const& p) const {
-        if (!Contains(p))
-        {
-            return nullptr;
-        }
-        
-        if (auto const childContainsIter = std::find_if(fChildNodes.cbegin(), fChildNodes.cend(),
-            [&] (auto const& s) {return bool(s->Contains(p));});
-            childContainsIter == fChildNodes.cend()) // not contained in any of the children
-        {
-            if (auto const exclContainsIter = Excludes(p)) // contained in any excluded nodes
-            {
-                return exclContainsIter->GetContainingNode(p);
-            }
-            else
-            {
-                return this;
-            }
-        }
-        else 
+    VolumeTreeNode<IModelProperties> const* GetContainingNode(
+        corsika::geometry::Point const& p) const {
+      if (!Contains(p)) { return nullptr; }
+
+      if (auto const childContainsIter =
+              std::find_if(fChildNodes.cbegin(), fChildNodes.cend(),
+                           [&](auto const& s) { return bool(s->Contains(p)); });
+          childContainsIter == fChildNodes.cend()) // not contained in any of the children
+      {
+        if (auto const exclContainsIter = Excludes(p)) // contained in any excluded nodes
         {
-            return (*childContainsIter)->GetContainingNode(p);
+          return exclContainsIter->GetContainingNode(p);
+        } else {
+          return this;
         }
+      } else {
+        return (*childContainsIter)->GetContainingNode(p);
+      }
     }
-    
+
     void addChild(VTNUPtr pChild) {
-        pChild->fParentNode = this;
-        fChildNodes.push_back(std::move(pChild));
-        // It is a bad idea to return an iterator to the inserted element
-        // because it might get invalidated when the vector needs to grow
-        // later and the caller won't notice.
-    }
-    
-    void excludeOverlapWith(VTNUPtr const& pNode) {        
-        fExcludedNodes.push_back(pNode.get());
-    }
-    
-    auto GetParent() const {
-        return fParentNode;
-    };
-    
-    auto const& GetVolume() const {
-        return *fGeoVolume;
+      pChild->fParentNode = this;
+      fChildNodes.push_back(std::move(pChild));
+      // It is a bad idea to return an iterator to the inserted element
+      // because it might get invalidated when the vector needs to grow
+      // later and the caller won't notice.
     }
-    
-    auto const& GetModelProperties() const {
-        return *fModelProperties;
+
+    void excludeOverlapWith(VTNUPtr const& pNode) {
+      fExcludedNodes.push_back(pNode.get());
     }
-    
+
+    auto GetParent() const { return fParentNode; };
+
+    auto const& GetVolume() const { return *fGeoVolume; }
+
+    auto const& GetModelProperties() const { return *fModelProperties; }
+
     template <typename ModelProperties, typename... Args>
     auto SetModelProperties(Args&&... args) {
-        static_assert(std::is_base_of_v<IModelProperties, ModelProperties>, "unusable type provided");
-        
-        fModelProperties = std::make_unique<ModelProperties>(std::forward<Args>(args)...);
-    }
-    
-    auto SetModelProperties(IMPSharedPtr ptr) {        
-        fModelProperties = IMPSharedPtr;
+      static_assert(std::is_base_of_v<IModelProperties, ModelProperties>,
+                    "unusable type provided");
+
+      fModelProperties = std::make_unique<ModelProperties>(std::forward<Args>(args)...);
     }
-    
+
+    auto SetModelProperties(IMPSharedPtr ptr) { fModelProperties = IMPSharedPtr; }
+
     template <class MediumType, typename... Args>
     static auto CreateMedium(Args&&... args) {
-        static_assert(std::is_base_of_v<IMediumModel, MediumType>, "unusable type provided, needs to be derived from \"IMediumModel\"");
-        
-        return std::make_shared<MediumType>(std::forward<Args>(args)...);
+      static_assert(std::is_base_of_v<IMediumModel, MediumType>,
+                    "unusable type provided, needs to be derived from \"IMediumModel\"");
+
+      return std::make_shared<MediumType>(std::forward<Args>(args)...);
     }
-        
+
     // factory methods for creation of nodes
     template <class VolumeType, typename... Args>
-    static auto CreateNode(Args&&...args) {
-        static_assert(std::is_base_of_v<Volume, VolumeType>, "unusable type provided, needs to be derived from \"Volume\"");
-        
-        return std::make_unique<VolumeTreeNode<IModelProperties>>(std::make_unique<VolumeType>(std::forward<Args>(args)...));
+    static auto CreateNode(Args&&... args) {
+      static_assert(std::is_base_of_v<Volume, VolumeType>,
+                    "unusable type provided, needs to be derived from \"Volume\"");
+
+      return std::make_unique<VolumeTreeNode<IModelProperties>>(
+          std::make_unique<VolumeType>(std::forward<Args>(args)...));
     }
-    
-private:
+
+  private:
     std::vector<VTNUPtr> fChildNodes;
     std::vector<VolumeTreeNode<IModelProperties> const*> fExcludedNodes;
     VolumeTreeNode<IModelProperties> const* fParentNode = nullptr;
     VolUPtr fGeoVolume;
-    std::shared_ptr<IModelProperties> fModelProperties;    
-};
+    std::shared_ptr<IModelProperties> fModelProperties;
+  };
 
-}
+} // namespace corsika::environment
 
 #endif
diff --git a/Framework/Units/PhysicalUnits.h b/Framework/Units/PhysicalUnits.h
index 5e7368dc6e62f0ce14c190e4f62144e3f4305b82..515db9e08415de1b91ec4a65d018dc50b173f902 100644
--- a/Framework/Units/PhysicalUnits.h
+++ b/Framework/Units/PhysicalUnits.h
@@ -38,6 +38,7 @@ namespace corsika::units::si {
   using EnergyType = phys::units::quantity<phys::units::energy_d, double>;
   using MassType = phys::units::quantity<phys::units::mass_d, double>;
   using MassDensityType = phys::units::quantity<phys::units::mass_density_d, double>;
+  using GrammageType = phys::units::quantity<phys::units::dimensions<-2,1>, double>;
 
 } // end namespace corsika::units::si